GDPR tool for Microsoft Dynamics AX 2012
The European Union has introduced strict data protection rules last month, for which companies had to become legally compliant to avoid fines. We have a set of patches to apply to get a GDPR tool for Microsoft Dynamics AX 2012, which has been released to assist us:
- KB4056903 Privacy Policy update
- KB4074643 DAPIA Security tool
- KB4057507 SQM Data collection
The part relevant for us is the tool, which allows capturing which interactive users have logged on to AX, who are using a security role that may access sensitive information.
Unfortunately Microsoft only provides a high-level guideline on what shall be included and provides very little tangible assistance. Due to this I have felt we needed some way to identify what security roles could really be accessing sensitive data, so I came up with an X++ job that does exactly this. You may pass in menu items for forms, reports and also tables that may access details such as Customers, Global Address Book, Vendors, Address and Contact details. The tool is using the Security framework to determine which roles can edit such data, but you may change filter criteria to also include View access.
static void WIK_GDPR_enable_roles(Args _args) { #AOT // List of tables which might contain sensitive data container tables = [ [menuitemDisplayStr(CustTable), UtilElementType::DisplayTool] ,[menuitemDisplayStr(CustTableListPage), UtilElementType::DisplayTool] ,[menuitemDisplayStr(CustTableEdit), UtilElementType::DisplayTool] ,[menuitemDisplayStr(CustTableDetails), UtilElementType::DisplayTool] ,[menuitemDisplayStr(GlobalAddressBookListPage), UtilElementType::DisplayTool] ,[menuitemDisplayStr(DirPartyTable), UtilElementType::DisplayTool] ,[menuitemDisplayStr(DirPartyTableEdit), UtilElementType::DisplayTool] ]; // Replace role settings? boolean update = NoYes::Yes; UtilElementType objectType; str objectName; int i = 1; SysSecFlatDataTable objects; SysSecFlatDataTable allObjects; SysUserLogRoleSettings roleSettings; SecurityRole securityRole; allObjects.setTmp(); while (i <= conLen(tables)) { objectName = conPeek(conPeek(tables, i), 1); objectType = conPeek(conPeek(tables, i), 2); switch (objectType) { // Implemented from \Forms\SysSecObjectsInRole\init case UtilElementType::DisplayTool: SysSecObjectsFromEntryPoint::GenerateData( SysSecObjectsAnalyzeType::SecViewRelatedRoles, objectName, enum2int(objectType)); break; case UtilElementType::OutputTool: SysSecObjectsFromEntryPoint::GenerateData( SysSecObjectsAnalyzeType::SecViewRelatedRoles, objectName, enum2int(objectType)); break; case UtilElementType::ActionTool: SysSecObjectsFromEntryPoint::GenerateData( SysSecObjectsAnalyzeType::SecViewRelatedRoles, objectName, enum2int(objectType)); break; case UtilElementType::Table: SysSecObjectsFromSecurableObject::GenerateData( objectName, enum2int(objectType)); break; } while select objects { allObjects.clear(); buf2Buf(objects, allObjects); allObjects.doInsert(); } i++; } if (update) { i = 0; ttsBegin; update_recordSet roleSettings setting HasAccessToSensitiveData = NoYes::No; // No join for Tmp object, must use nested loop while select allObjects group by Role//, IsOverride where allObjects.IsOverride == NoYes::No && ((allObjects.AccessRight != AccessRight::View && allObjects.AccessRight != AccessRight::NoAccess) && (allObjects.EntryPointAccess != AccessRight::View && allObjects.EntryPointAccess != AccessRight::NoAccess)) { select firstOnly forUpdate roleSettings join RecId from securityRole where securityRole.AotName == allObjects.Role && roleSettings.SecurityRole == securityRole.RecId; if (roleSettings) { roleSettings.HasAccessToSensitiveData = NoYes::Yes; roleSettings.doUpdate(); i++; } } ttsCommit; info(strFmt('%1 security roles have been updated', i)); } while select Role, RoleName from allObjects group by RoleName, Role//, AccessRight, EntryPointAccess where allObjects.IsOverride == NoYes::No && ((allObjects.AccessRight != AccessRight::View && allObjects.AccessRight != AccessRight::NoAccess) && (allObjects.EntryPointAccess != AccessRight::View && allObjects.EntryPointAccess != AccessRight::NoAccess)) { info(strFmt('%1 (%2)', allObjects.Role, allObjects.RoleName)); } }
The XPO could be downloaded from GitHub.