Guest User

Untitled

a guest
Dec 1st, 2025
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 21.23 KB | Source Code | 0 0
  1. <?php
  2. /**
  3.  * EspoCRM Access Control Overview for ISO27001 Audit
  4.  * run via CLI, output to HTML and JSON
  5.  *
  6.  * This script generates a report with
  7.  * - Users and their roles
  8.  * - Teams and memberships
  9.  * - Role permissions (entity and field level)
  10.  * - Access control matrix
  11.  */
  12.  
  13. require_once 'bootstrap.php';
  14.  
  15. class EspoACLAuditor
  16. {
  17.     private $entityManager;
  18.     private $config;
  19.     private $metadata;
  20.    
  21.     public function __construct()
  22.     {
  23.         $app = new \Espo\Core\Application();
  24.         $this->entityManager = $app->getContainer()->get('entityManager');
  25.         $this->config = $app->getContainer()->get('config');
  26.         $this->metadata = $app->getContainer()->get('metadata');
  27.     }
  28.    
  29.     public function generateAuditReport()
  30.     {
  31.         $report = [
  32.             'generated_at' => date('Y-m-d H:i:s'),
  33.             'system_info' => $this->getSystemInfo(),
  34.             'users' => $this->getUsersOverview(),
  35.             'roles' => $this->getRolesOverview(),
  36.             'teams' => $this->getTeamsOverview(),
  37.             'access_matrix' => $this->generateAccessMatrix(),
  38.             'field_level_security' => $this->getFieldLevelSecurity(),
  39.             'audit_findings' => $this->performAuditChecks()
  40.         ];
  41.        
  42.         return $report;
  43.     }
  44.    
  45.     private function getSystemInfo()
  46.     {
  47.         return [
  48.             'espo_version' => $this->config->get('version'),
  49.             'site_url' => $this->config->get('siteUrl'),
  50.             'total_users' => $this->entityManager->getRepository('User')->count(),
  51.             'active_users' => $this->entityManager->getRepository('User')
  52.                 ->where(['isActive' => true])->count(),
  53.             'total_roles' => $this->entityManager->getRepository('Role')->count(),
  54.             'total_teams' => $this->entityManager->getRepository('Team')->count()
  55.         ];
  56.     }
  57.    
  58.     private function getUsersOverview()
  59.     {
  60.         $users = $this->entityManager->getRepository('User')
  61.             ->find(['orderBy' => 'userName']);
  62.        
  63.         $userList = [];
  64.         foreach ($users as $user) {
  65.             $teams = $this->entityManager->getRepository('User')
  66.                 ->getRelation($user, 'teams')->find();
  67.            
  68.             $roles = $this->entityManager->getRepository('User')
  69.                 ->getRelation($user, 'roles')->find();
  70.            
  71.             $userList[] = [
  72.                 'id' => $user->get('id'),
  73.                 'username' => $user->get('userName'),
  74.                 'name' => $user->get('name'),
  75.                 'email' => $user->get('emailAddress'),
  76.                 'is_active' => $user->get('isActive'),
  77.                 'is_admin' => $user->get('isAdmin'),
  78.                 'type' => $user->get('type'),
  79.                 'created_at' => $user->get('createdAt'),
  80.                 'last_access' => $user->get('lastAccess'),
  81.                 'teams' => array_map(function($team) {
  82.                     return [
  83.                         'id' => $team->get('id'),
  84.                         'name' => $team->get('name')
  85.                     ];
  86.                 }, iterator_to_array($teams)),
  87.                 'roles' => array_map(function($role) {
  88.                     return [
  89.                         'id' => $role->get('id'),
  90.                         'name' => $role->get('name')
  91.                     ];
  92.                 }, iterator_to_array($roles))
  93.             ];
  94.         }
  95.        
  96.         return $userList;
  97.     }
  98.    
  99.     private function getRolesOverview()
  100.     {
  101.         $roles = $this->entityManager->getRepository('Role')->find();
  102.        
  103.         $roleList = [];
  104.         foreach ($roles as $role) {
  105.             $data = $role->get('data');
  106.            
  107.             $roleList[] = [
  108.                 'id' => $role->get('id'),
  109.                 'name' => $role->get('name'),
  110.                 'assignment_permission' => $role->get('assignmentPermission'),
  111.                 'user_permission' => $role->get('userPermission'),
  112.                 'portal_permission' => $role->get('portalPermission'),
  113.                 'data_privacy_permission' => $role->get('dataPrivacyPermission'),
  114.                 'scope_level_permissions' => $this->parseScopePermissions($data),
  115.                 'field_level_permissions' => $this->parseFieldPermissions($data)
  116.             ];
  117.         }
  118.        
  119.         return $roleList;
  120.     }
  121.    
  122.     private function parseScopePermissions($data)
  123.     {
  124.         $permissions = [];
  125.        
  126.         if (!$data || !is_object($data)) {
  127.             return $permissions;
  128.         }
  129.        
  130.         foreach (get_object_vars($data) as $scope => $scopeData) {
  131.             if (is_object($scopeData) && !isset($scopeData->fields)) {
  132.                 $permissions[$scope] = [
  133.                     'create' => $scopeData->create ?? 'no',
  134.                     'read' => $scopeData->read ?? 'no',
  135.                     'edit' => $scopeData->edit ?? 'no',
  136.                     'delete' => $scopeData->delete ?? 'no',
  137.                     'stream' => $scopeData->stream ?? 'no'
  138.                 ];
  139.             }
  140.         }
  141.        
  142.         return $permissions;
  143.     }
  144.    
  145.     private function parseFieldPermissions($data)
  146.     {
  147.         $permissions = [];
  148.        
  149.         if (!$data || !is_object($data)) {
  150.             return $permissions;
  151.         }
  152.        
  153.         foreach (get_object_vars($data) as $scope => $scopeData) {
  154.             if (is_object($scopeData) && isset($scopeData->fields)) {
  155.                 $permissions[$scope] = [];
  156.                 foreach (get_object_vars($scopeData->fields) as $field => $fieldData) {
  157.                     $permissions[$scope][$field] = [
  158.                         'read' => $fieldData->read ?? 'yes',
  159.                         'edit' => $fieldData->edit ?? 'yes'
  160.                     ];
  161.                 }
  162.             }
  163.         }
  164.        
  165.         return $permissions;
  166.     }
  167.    
  168.     private function getTeamsOverview()
  169.     {
  170.         $teams = $this->entityManager->getRepository('Team')->find();
  171.        
  172.         $teamList = [];
  173.         foreach ($teams as $team) {
  174.             $users = $this->entityManager->getRepository('Team')
  175.                 ->getRelation($team, 'users')->find();
  176.            
  177.             $teamList[] = [
  178.                 'id' => $team->get('id'),
  179.                 'name' => $team->get('name'),
  180.                 'created_at' => $team->get('createdAt'),
  181.                 'users' => array_map(function($user) {
  182.                     return [
  183.                         'id' => $user->get('id'),
  184.                         'username' => $user->get('userName'),
  185.                         'name' => $user->get('name')
  186.                     ];
  187.                 }, iterator_to_array($users)),
  188.                 'user_count' => count(iterator_to_array($users))
  189.             ];
  190.         }
  191.        
  192.         return $teamList;
  193.     }
  194.    
  195.     private function generateAccessMatrix()
  196.     {
  197.         $entities = $this->metadata->get(['entityDefs']);
  198.         $roles = $this->entityManager->getRepository('Role')->find();
  199.        
  200.         $matrix = [];
  201.        
  202.         foreach ($roles as $role) {
  203.             $data = $role->get('data');
  204.             $roleName = $role->get('name');
  205.            
  206.             foreach (array_keys($entities) as $entityType) {
  207.                 if (!isset($matrix[$entityType])) {
  208.                     $matrix[$entityType] = [];
  209.                 }
  210.                
  211.                 $permissions = 'no';
  212.                 if ($data && is_object($data) && isset($data->$entityType)) {
  213.                     $scopeData = $data->$entityType;
  214.                     $permissions = sprintf(
  215.                         'C:%s R:%s E:%s D:%s',
  216.                         $scopeData->create ?? 'no',
  217.                         $scopeData->read ?? 'no',
  218.                         $scopeData->edit ?? 'no',
  219.                         $scopeData->delete ?? 'no'
  220.                     );
  221.                 }
  222.                
  223.                 $matrix[$entityType][$roleName] = $permissions;
  224.             }
  225.         }
  226.        
  227.         return $matrix;
  228.     }
  229.    
  230.     private function getFieldLevelSecurity()
  231.     {
  232.         $roles = $this->entityManager->getRepository('Role')->find();
  233.         $fieldSecurity = [];
  234.        
  235.         foreach ($roles as $role) {
  236.             $data = $role->get('data');
  237.             $roleName = $role->get('name');
  238.            
  239.             if (!$data || !is_object($data)) {
  240.                 continue;
  241.             }
  242.            
  243.             foreach (get_object_vars($data) as $scope => $scopeData) {
  244.                 if (is_object($scopeData) && isset($scopeData->fields)) {
  245.                     if (!isset($fieldSecurity[$scope])) {
  246.                         $fieldSecurity[$scope] = [];
  247.                     }
  248.                    
  249.                     foreach (get_object_vars($scopeData->fields) as $field => $fieldData) {
  250.                         if (!isset($fieldSecurity[$scope][$field])) {
  251.                             $fieldSecurity[$scope][$field] = [];
  252.                         }
  253.                        
  254.                         $fieldSecurity[$scope][$field][$roleName] = [
  255.                             'read' => $fieldData->read ?? 'yes',
  256.                             'edit' => $fieldData->edit ?? 'yes'
  257.                         ];
  258.                     }
  259.                 }
  260.             }
  261.         }
  262.        
  263.         return $fieldSecurity;
  264.     }
  265.    
  266.     private function performAuditChecks()
  267.     {
  268.         $findings = [];
  269.        
  270.         // Check for users without roles
  271.         $usersWithoutRoles = $this->entityManager->getRepository('User')
  272.             ->where(['isActive' => true, 'type' => 'regular'])
  273.             ->find();
  274.        
  275.         foreach ($usersWithoutRoles as $user) {
  276.             $roles = $this->entityManager->getRepository('User')
  277.                 ->getRelation($user, 'roles')->find();
  278.            
  279.             if (count(iterator_to_array($roles)) === 0 && !$user->get('isAdmin')) {
  280.                 $findings[] = [
  281.                     'severity' => 'HIGH',
  282.                     'type' => 'USER_WITHOUT_ROLE',
  283.                     'description' => "User '{$user->get('userName')}' has no assigned roles",
  284.                     'user_id' => $user->get('id')
  285.                 ];
  286.             }
  287.         }
  288.        
  289.         // Check for inactive users with recent access
  290.         $inactiveUsers = $this->entityManager->getRepository('User')
  291.             ->where(['isActive' => false])
  292.             ->find();
  293.        
  294.         foreach ($inactiveUsers as $user) {
  295.             $lastAccess = $user->get('lastAccess');
  296.             if ($lastAccess && strtotime($lastAccess) > strtotime('-30 days')) {
  297.                 $findings[] = [
  298.                     'severity' => 'MEDIUM',
  299.                     'type' => 'INACTIVE_USER_RECENT_ACCESS',
  300.                     'description' => "Inactive user '{$user->get('userName')}' has recent access",
  301.                     'last_access' => $lastAccess
  302.                 ];
  303.             }
  304.         }
  305.        
  306.         // Check for admin users
  307.         $adminUsers = $this->entityManager->getRepository('User')
  308.             ->where(['isAdmin' => true, 'isActive' => true])
  309.             ->find();
  310.        
  311.         $findings[] = [
  312.             'severity' => 'INFO',
  313.             'type' => 'ADMIN_USERS_COUNT',
  314.             'description' => "Total active admin users: " . count(iterator_to_array($adminUsers)),
  315.             'count' => count(iterator_to_array($adminUsers))
  316.         ];
  317.        
  318.         // Check for users without teams
  319.         $usersWithoutTeams = $this->entityManager->getRepository('User')
  320.             ->where(['isActive' => true, 'type' => 'regular'])
  321.             ->find();
  322.        
  323.         foreach ($usersWithoutTeams as $user) {
  324.             $teams = $this->entityManager->getRepository('User')
  325.                 ->getRelation($user, 'teams')->find();
  326.            
  327.             if (count(iterator_to_array($teams)) === 0) {
  328.                 $findings[] = [
  329.                     'severity' => 'LOW',
  330.                     'type' => 'USER_WITHOUT_TEAM',
  331.                     'description' => "User '{$user->get('userName')}' is not assigned to any team",
  332.                     'user_id' => $user->get('id')
  333.                 ];
  334.             }
  335.         }
  336.        
  337.         return $findings;
  338.     }
  339.    
  340.     public function exportToJSON($filename = 'espo_acl_audit_report.json')
  341.     {
  342.         $report = $this->generateAuditReport();
  343.         file_put_contents($filename, json_encode($report, JSON_PRETTY_PRINT));
  344.         return $filename;
  345.     }
  346.    
  347.     public function exportToHTML($filename = 'espo_acl_audit_report.html')
  348.     {
  349.         $report = $this->generateAuditReport();
  350.        
  351.         $html = $this->generateHTMLReport($report);
  352.         file_put_contents($filename, $html);
  353.         return $filename;
  354.     }
  355.    
  356.     private function generateHTMLReport($report)
  357.     {
  358.         ob_start();
  359.         ?>
  360. <!DOCTYPE html>
  361. <html>
  362. <head>
  363.     <title>EspoCRM Access Control Audit Report - ISO27001</title>
  364.     <style>
  365.         body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
  366.         .container { max-width: 1400px; margin: 0 auto; background: white; padding: 30px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
  367.         h1 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; }
  368.         h2 { color: #34495e; margin-top: 30px; border-bottom: 2px solid #ecf0f1; padding-bottom: 8px; }
  369.         h3 { color: #7f8c8d; }
  370.         table { width: 100%; border-collapse: collapse; margin: 20px 0; }
  371.         th { background: #3498db; color: white; padding: 12px; text-align: left; }
  372.         td { padding: 10px; border: 1px solid #ddd; }
  373.         tr:nth-child(even) { background: #f9f9f9; }
  374.         .info-box { background: #e8f4f8; padding: 15px; margin: 15px 0; border-left: 4px solid #3498db; }
  375.         .severity-HIGH { color: #e74c3c; font-weight: bold; }
  376.         .severity-MEDIUM { color: #f39c12; font-weight: bold; }
  377.         .severity-LOW { color: #95a5a6; }
  378.         .severity-INFO { color: #3498db; }
  379.         .badge { display: inline-block; padding: 3px 8px; border-radius: 3px; font-size: 12px; }
  380.         .badge-active { background: #2ecc71; color: white; }
  381.         .badge-inactive { background: #e74c3c; color: white; }
  382.         .badge-admin { background: #9b59b6; color: white; }
  383.         .matrix-table { font-size: 11px; }
  384.         .matrix-table td { padding: 5px; }
  385.         .permission { font-family: monospace; font-size: 10px; }
  386.     </style>
  387. </head>
  388. <body>
  389.     <div class="container">
  390.         <h1>EspoCRM Access Control Audit Report</h1>
  391.         <p><strong>Generated:</strong> <?= htmlspecialchars($report['generated_at']) ?></p>
  392.         <p><strong>Purpose:</strong> ISO27001 Internal Audit - Access Control Review</p>
  393.        
  394.         <h2>1. System Overview</h2>
  395.         <div class="info-box">
  396.             <p><strong>EspoCRM Version:</strong> <?= htmlspecialchars($report['system_info']['espo_version']) ?></p>
  397.             <p><strong>Site URL:</strong> <?= htmlspecialchars($report['system_info']['site_url']) ?></p>
  398.             <p><strong>Total Users:</strong> <?= $report['system_info']['total_users'] ?> (Active: <?= $report['system_info']['active_users'] ?>)</p>
  399.             <p><strong>Total Roles:</strong> <?= $report['system_info']['total_roles'] ?></p>
  400.             <p><strong>Total Teams:</strong> <?= $report['system_info']['total_teams'] ?></p>
  401.         </div>
  402.        
  403.         <h2>2. Audit Findings</h2>
  404.         <table>
  405.             <tr>
  406.                 <th>Severity</th>
  407.                 <th>Type</th>
  408.                 <th>Description</th>
  409.             </tr>
  410.             <?php foreach ($report['audit_findings'] as $finding): ?>
  411.             <tr>
  412.                 <td class="severity-<?= $finding['severity'] ?>"><?= $finding['severity'] ?></td>
  413.                 <td><?= htmlspecialchars($finding['type']) ?></td>
  414.                 <td><?= htmlspecialchars($finding['description']) ?></td>
  415.             </tr>
  416.             <?php endforeach; ?>
  417.         </table>
  418.        
  419.         <h2>3. Users Overview</h2>
  420.         <table>
  421.             <tr>
  422.                 <th>Username</th>
  423.                 <th>Name</th>
  424.                 <th>Status</th>
  425.                 <th>Type</th>
  426.                 <th>Roles</th>
  427.                 <th>Teams</th>
  428.                 <th>Last Access</th>
  429.             </tr>
  430.             <?php foreach ($report['users'] as $user): ?>
  431.             <tr>
  432.                 <td><?= htmlspecialchars($user['username']) ?></td>
  433.                 <td><?= htmlspecialchars($user['name']) ?></td>
  434.                 <td>
  435.                     <?php if ($user['is_active']): ?>
  436.                         <span class="badge badge-active">Active</span>
  437.                     <?php else: ?>
  438.                         <span class="badge badge-inactive">Inactive</span>
  439.                     <?php endif; ?>
  440.                     <?php if ($user['is_admin']): ?>
  441.                         <span class="badge badge-admin">Admin</span>
  442.                     <?php endif; ?>
  443.                 </td>
  444.                 <td><?= htmlspecialchars($user['type']) ?></td>
  445.                 <td><?= implode(', ', array_column($user['roles'], 'name')) ?></td>
  446.                 <td><?= implode(', ', array_column($user['teams'], 'name')) ?></td>
  447.                 <td><?= htmlspecialchars($user['last_access'] ?? 'Never') ?></td>
  448.             </tr>
  449.             <?php endforeach; ?>
  450.         </table>
  451.        
  452.         <h2>4. Roles Overview</h2>
  453.         <?php foreach ($report['roles'] as $role): ?>
  454.         <h3><?= htmlspecialchars($role['name']) ?></h3>
  455.         <div class="info-box">
  456.             <p><strong>Assignment Permission:</strong> <?= htmlspecialchars($role['assignment_permission'] ?? 'N/A') ?></p>
  457.             <p><strong>User Permission:</strong> <?= htmlspecialchars($role['user_permission'] ?? 'N/A') ?></p>
  458.         </div>
  459.        
  460.         <?php if (!empty($role['scope_level_permissions'])): ?>
  461.         <h4>Entity Permissions</h4>
  462.         <table>
  463.             <tr>
  464.                 <th>Entity</th>
  465.                 <th>Create</th>
  466.                 <th>Read</th>
  467.                 <th>Edit</th>
  468.                 <th>Delete</th>
  469.                 <th>Stream</th>
  470.             </tr>
  471.             <?php foreach ($role['scope_level_permissions'] as $entity => $perms): ?>
  472.             <tr>
  473.                 <td><?= htmlspecialchars($entity) ?></td>
  474.                 <td><?= htmlspecialchars($perms['create']) ?></td>
  475.                 <td><?= htmlspecialchars($perms['read']) ?></td>
  476.                 <td><?= htmlspecialchars($perms['edit']) ?></td>
  477.                 <td><?= htmlspecialchars($perms['delete']) ?></td>
  478.                 <td><?= htmlspecialchars($perms['stream']) ?></td>
  479.             </tr>
  480.             <?php endforeach; ?>
  481.         </table>
  482.         <?php endif; ?>
  483.         <?php endforeach; ?>
  484.        
  485.         <h2>5. Teams Overview</h2>
  486.         <table>
  487.             <tr>
  488.                 <th>Team Name</th>
  489.                 <th>User Count</th>
  490.                 <th>Members</th>
  491.                 <th>Created</th>
  492.             </tr>
  493.             <?php foreach ($report['teams'] as $team): ?>
  494.             <tr>
  495.                 <td><?= htmlspecialchars($team['name']) ?></td>
  496.                 <td><?= $team['user_count'] ?></td>
  497.                 <td><?= implode(', ', array_column($team['users'], 'username')) ?></td>
  498.                 <td><?= htmlspecialchars($team['created_at']) ?></td>
  499.             </tr>
  500.             <?php endforeach; ?>
  501.         </table>
  502.        
  503.         <h2>6. Access Control Matrix</h2>
  504.         <p><em>Format: C=Create, R=Read, E=Edit, D=Delete</em></p>
  505.         <table class="matrix-table">
  506.             <tr>
  507.                 <th>Entity</th>
  508.                 <?php foreach ($report['roles'] as $role): ?>
  509.                 <th><?= htmlspecialchars($role['name']) ?></th>
  510.                 <?php endforeach; ?>
  511.             </tr>
  512.             <?php foreach ($report['access_matrix'] as $entity => $rolePerms): ?>
  513.             <tr>
  514.                 <td><strong><?= htmlspecialchars($entity) ?></strong></td>
  515.                 <?php foreach ($report['roles'] as $role): ?>
  516.                 <td class="permission"><?= htmlspecialchars($rolePerms[$role['name']] ?? 'N/A') ?></td>
  517.                 <?php endforeach; ?>
  518.             </tr>
  519.             <?php endforeach; ?>
  520.         </table>
  521.        
  522.         <h2>7. ISO27001 Compliance Notes</h2>
  523.         <div class="info-box">
  524.             <p><strong>A.9.2.1 User Registration:</strong> Review user creation dates and approval process</p>
  525.             <p><strong>A.9.2.2 User Access Provisioning:</strong> Verify role assignments match job functions</p>
  526.             <p><strong>A.9.2.3 Management of Privileged Access:</strong> Review admin user list and justification</p>
  527.             <p><strong>A.9.2.5 Review of User Access Rights:</strong> Conduct periodic access reviews</p>
  528.             <p><strong>A.9.2.6 Removal of Access Rights:</strong> Verify inactive users have no recent access</p>
  529.         </div>
  530.     </div>
  531. </body>
  532. </html>
  533.         <?php
  534.         return ob_get_clean();
  535.     }
  536. }
  537.  
  538. // Usage
  539. try {
  540.     $auditor = new EspoACLAuditor();
  541.    
  542.     // Generate JSON report
  543.     $jsonFile = $auditor->exportToJSON();
  544.     echo "JSON report generated: $jsonFile\n";
  545.    
  546.     // Generate HTML report
  547.     $htmlFile = $auditor->exportToHTML();
  548.     echo "HTML report generated: $htmlFile\n";
  549.    
  550. } catch (Exception $e) {
  551.     echo "Error: " . $e->getMessage() . "\n";
  552. }
  553.  
Tags: espocrm
Advertisement
Add Comment
Please, Sign In to add comment