PageRenderTime 39ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Plugin/monitoring/SensorPlugin/UserIntegritySensorPlugin.php

https://gitlab.com/Drulenium-bot/monitoring
PHP | 442 lines | 287 code | 48 blank | 107 comment | 32 complexity | 8514ab40e4223a7cdeb669243ef77084 MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\monitoring\Plugin\monitoring\SensorPlugin\UserIntegritySensorPlugin.
  5. */
  6. namespace Drupal\monitoring\Plugin\monitoring\SensorPlugin;
  7. use Drupal\Core\Form\FormStateInterface;
  8. use Drupal\monitoring\SensorPlugin\ExtendedInfoSensorPluginInterface;
  9. use Drupal\monitoring\SensorPlugin\SensorPluginBase;
  10. use Drupal\user\Entity\User;
  11. use Drupal\monitoring\Result\SensorResultInterface;
  12. use Drupal\user\Entity\Role;
  13. /**
  14. * Monitors user data changes.
  15. *
  16. * A custom database query is used here instead of entity manager for
  17. * performance reasons.
  18. *
  19. * @SensorPlugin(
  20. * id = "user_integrity",
  21. * label = @Translation("Privileged user integrity"),
  22. * description = @Translation("Monitors name and e-mail changes of users with access to restricted permissions. Checks if authenticated or anonymous users have privileged access."),
  23. * addable = FALSE
  24. * )
  25. */
  26. class UserIntegritySensorPlugin extends SensorPluginBase implements ExtendedInfoSensorPluginInterface {
  27. /**
  28. * {@inheritdoc}
  29. */
  30. protected $configurableValueType = FALSE;
  31. /**
  32. * The max number of users to list in the verbose output.
  33. *
  34. * @var int
  35. */
  36. protected $listSize = 500;
  37. /**
  38. * The list of restricted permissions.
  39. *
  40. * @var array
  41. */
  42. protected $restrictedPermissions = array();
  43. /**
  44. * {@inheritdoc}
  45. */
  46. public function runSensor(SensorResultInterface $sensor_result) {
  47. // Get role IDs with restricted permissions.
  48. $role_ids = $this->getRestrictedRoles();
  49. $current_users = $this->processUsers($this->loadCurrentUsers($role_ids));
  50. // Add sensor message, count of current privileged users.
  51. $sensor_result->addStatusMessage(count($current_users) . ' privileged user(s)');
  52. $sensor_result->setStatus(SensorResultInterface::STATUS_OK);
  53. // Load old user data.
  54. $old_users = \Drupal::keyValue('monitoring.users')->getAll();
  55. // If user data is not stored, store them first.
  56. if (empty($old_users)) {
  57. foreach ($current_users as $user) {
  58. \Drupal::keyValue('monitoring.users')->set($user['id'], $user);
  59. }
  60. }
  61. // Check for new users and changes in existing users.
  62. else {
  63. $new_users = array_diff_key($current_users, $old_users);
  64. if (!empty($new_users)) {
  65. $sensor_result->setStatus(SensorResultInterface::STATUS_WARNING);
  66. $sensor_result->addStatusMessage(count($new_users) . ' new user(s)');
  67. }
  68. // Get the count of privileged users with changes.
  69. $count = 0;
  70. $changed_users_ids = array_intersect(array_keys($current_users), array_keys($old_users));
  71. foreach ($changed_users_ids as $changed_user_id) {
  72. $changed = $this->getUserChanges($current_users[$changed_user_id], $old_users[$changed_user_id]);
  73. if (!empty($changed)) {
  74. $count++;
  75. }
  76. }
  77. if ($count > 0) {
  78. $sensor_result->addStatusMessage($count . ' changed user(s)');
  79. $sensor_result->setStatus(SensorResultInterface::STATUS_WARNING);
  80. }
  81. }
  82. // Check anonymous and authenticated users with restricted permissions and
  83. // show a message.
  84. $user_register = \Drupal::config('user.settings')->get('register');
  85. // Check if authenticated or anonymous users have restrict access perms.
  86. $role_ids_after = array_intersect($role_ids, ['authenticated', 'anonymous']);
  87. $role_labels = [];
  88. foreach (Role::loadMultiple($role_ids_after) as $role) {
  89. $role_labels[$role->id()] = $role->label();
  90. }
  91. if (!empty($role_labels)) {
  92. $sensor_result->addStatusMessage('Privileged access for roles @roles', array('@roles' => implode(', ', $role_labels)));
  93. $sensor_result->setStatus(SensorResultInterface::STATUS_WARNING);
  94. }
  95. // Further escalate if the restricted access is for anonymous.
  96. if ((in_array('anonymous', $role_ids))) {
  97. $sensor_result->setStatus(SensorResultInterface::STATUS_CRITICAL);
  98. }
  99. // Check if self registration is possible.
  100. if ((in_array('authenticated', $role_ids)) && $user_register != USER_REGISTER_ADMINISTRATORS_ONLY) {
  101. $sensor_result->addStatusMessage('Self registration possible.');
  102. $sensor_result->setStatus(SensorResultInterface::STATUS_CRITICAL);
  103. }
  104. }
  105. /**
  106. * Gets a list of available users.
  107. *
  108. * @param string[] $role_ids
  109. * Roles to filter users.
  110. *
  111. * @return \Drupal\user\Entity\User[]
  112. * Available users.
  113. */
  114. protected function loadCurrentUsers(array $role_ids) {
  115. if (!$role_ids) {
  116. return [];
  117. }
  118. // Loading all users and managing them will kill the system so we limit
  119. // them.
  120. $query = \Drupal::entityQuery('user')
  121. ->sort('access', 'DESC')
  122. ->range(0, $this->listSize);
  123. // The authenticated role is not persisted and it could have restrict access
  124. // so we load every user.
  125. if (in_array('authenticated', $role_ids)) {
  126. $uids = $query->condition('uid', '0', '<>')
  127. ->execute();
  128. }
  129. else {
  130. // Load all users with the roles.
  131. $uids = $query->condition('roles', $role_ids, 'IN')
  132. ->execute();
  133. }
  134. return User::loadMultiple($uids);
  135. }
  136. /**
  137. * Gets changes made to user data.
  138. *
  139. * @param array $current_values
  140. * Current user data returned by ::processUsers().
  141. * @param array $expected_values
  142. * Expected user data returned by ::processUsers().
  143. *
  144. * @return string[][]
  145. * Changes in user.
  146. */
  147. protected function getUserChanges(array $current_values, array $expected_values) {
  148. $changes = array();
  149. if ($current_values['name'] != $expected_values['name']) {
  150. $changes['name']['expected_value'] = $expected_values['name'];
  151. $changes['name']['current_value'] = $current_values['name'];
  152. }
  153. if ($current_values['mail'] != $expected_values['mail']) {
  154. $changes['mail']['expected_value'] = $expected_values['mail'];
  155. $changes['mail']['current_value'] = $current_values['mail'];
  156. }
  157. if ($current_values['password'] != $expected_values['password']) {
  158. $changes['password']['expected_value'] = '';
  159. $changes['password']['current_value'] = t('Password changed');
  160. }
  161. return $changes;
  162. }
  163. /**
  164. * Gets a list of restricted roles.
  165. *
  166. * @return string[]
  167. * Restricted roles.
  168. */
  169. protected function getRestrictedRoles() {
  170. /** @var \Drupal\user\PermissionHandlerInterface $permission_handler */
  171. $permission_handler = \Drupal::service('user.permissions');
  172. $available_permissions = $permission_handler->getPermissions();;
  173. $this->restrictedPermissions = array();
  174. foreach ($available_permissions as $key => $value) {
  175. if (!empty($value['restrict access'])) {
  176. $this->restrictedPermissions[] = $key;
  177. }
  178. }
  179. $avaliable_roles = Role::loadMultiple();
  180. $restricted_roles = array();
  181. foreach ($avaliable_roles as $role) {
  182. $permissions = $role->getPermissions();
  183. if ($role->isAdmin() || array_intersect($permissions, $this->restrictedPermissions)) {
  184. $restricted_roles[] = $role->id();
  185. }
  186. }
  187. return $restricted_roles;
  188. }
  189. /**
  190. * Process user entity into raw value array.
  191. *
  192. * @param \Drupal\user\Entity\User[] $users
  193. * Users to process.
  194. *
  195. * @return array
  196. * Processed user data, list of arrays with keys id, name, mail, password
  197. * ans changed time.
  198. */
  199. protected function processUsers(array $users) {
  200. $processed_users = array();
  201. foreach ($users as $user) {
  202. $id = $user->id();
  203. $processed_users[$id]['id'] = $id;
  204. $processed_users[$id]['name'] = $user->getUsername();
  205. $processed_users[$id]['mail'] = $user->getEmail();
  206. $processed_users[$id]['password'] = hash('sha256', $user->getPassword());
  207. $processed_users[$id]['changed'] = $user->getChangedTime();
  208. $processed_users[$id]['last_accessed'] = $user->getLastAccessedTime();
  209. $processed_users[$id]['created'] = $user->getCreatedTime();
  210. $processed_users[$id]['roles'] = implode(", ", $user->getRoles());
  211. }
  212. return $processed_users;
  213. }
  214. /**
  215. * {@inheritdoc}
  216. */
  217. public function resultVerbose(SensorResultInterface $result) {
  218. $output = [];
  219. // Load all the old user data.
  220. $expected_users = \Drupal::keyValue('monitoring.users')->getAll();
  221. // Get available roles with restricted permission.
  222. $role_ids = $this->getRestrictedRoles();
  223. // Process the current user data.
  224. $current_users = $this->processUsers($this->loadCurrentUsers($role_ids));
  225. $new_users_id = array_diff(array_keys($current_users), array_keys($expected_users));
  226. $deleted_users = array_diff_key($expected_users, $current_users);
  227. // Verbose output for new users.
  228. $rows = [];
  229. foreach ($new_users_id as $id) {
  230. $time_stamp = $current_users[$id]['created'];
  231. $last_accessed = $current_users[$id]['last_accessed'];
  232. // Do this for all, and delete drupal render.
  233. $user_name = [
  234. 'data' => [
  235. '#theme' => 'username',
  236. '#account' => User::load($id),
  237. ]
  238. ];
  239. $rows[] = [
  240. 'user' => $user_name,
  241. 'roles' => ['data' => ['#markup' => $current_users[$id]['roles']]],
  242. 'created' => ['data' => ['#markup' => \Drupal::service('date.formatter')->format($time_stamp, 'short')]],
  243. 'last_accessed' => ['data' => ['#markup' => $last_accessed != 0 ? \Drupal::service('date.formatter')->format($last_accessed, 'short') : t('never')]],
  244. ];
  245. }
  246. if (count($rows) > 0) {
  247. $header = [
  248. 'user' => t('User'),
  249. 'roles' => t('Roles'),
  250. 'created' => t('Created'),
  251. 'last_accessed' => t('Last accessed'),
  252. ];
  253. $output['new_table'] = [
  254. '#type' => 'verbose_table_result',
  255. '#title' => t('New users with privileged access'),
  256. '#header' => $header,
  257. '#rows' => $rows,
  258. ];
  259. }
  260. // Verbose output for users with changes.
  261. $rows = [];
  262. $old_user_ids = array_keys($expected_users);
  263. foreach ($old_user_ids as $id) {
  264. $changes = [];
  265. if (isset($current_users[$id])) {
  266. $changes = $this->getUserChanges($current_users[$id], $expected_users[$id]);
  267. }
  268. foreach ($changes as $key => $value) {
  269. $time_stamp = $current_users[$id]['changed'];
  270. $last_accessed = $current_users[$id]['last_accessed'];
  271. $user_name = [
  272. 'data' => [
  273. '#theme' => 'username',
  274. '#account' => User::load($id),
  275. ]
  276. ];
  277. $rows[] = [
  278. 'user' => $user_name,
  279. 'field' => ['data' => ['#markup' => $key]],
  280. 'current_value' => ['data' => ['#markup' => $value['current_value']]],
  281. 'expected_value' => ['data' => ['#markup' => $value['expected_value']]],
  282. 'changed' => ['data' => ['#markup' => \Drupal::service('date.formatter')->format($time_stamp, 'short')]],
  283. 'last_accessed' => ['data' => ['#markup' => $last_accessed != 0 ? \Drupal::service('date.formatter')->format($last_accessed, 'short') : t('never')]],
  284. ];
  285. }
  286. }
  287. if (count($rows) > 0) {
  288. $header = [
  289. 'user' => t('User'),
  290. 'Field' => t('Field'),
  291. 'current_value' => t('Current value'),
  292. 'expected_value' => t('Expected value'),
  293. 'changed' => t('Changed'),
  294. 'last_accessed' => t('Last accessed'),
  295. ];
  296. $output['changes_table'] = [
  297. '#type' => 'verbose_table_result',
  298. '#title' => t('Changed users with privileged access'),
  299. '#header' => $header,
  300. '#rows' => $rows,
  301. ];
  302. }
  303. // Verbose output for all privileged users.
  304. $rows = [];
  305. foreach ($current_users as $user) {
  306. $created = $user['created'];
  307. $user_name = [
  308. 'data' => [
  309. '#theme' => 'username',
  310. '#account' => User::load($user['id']),
  311. ]
  312. ];
  313. $rows[] = [
  314. 'user' => $user_name,
  315. 'roles' => ['data' => ['#markup' => $user['roles']]],
  316. 'created' => ['data' => ['#markup' => \Drupal::service('date.formatter')->format($created, 'short')]],
  317. 'last_accessed' => ['data' => ['#markup' => $user['last_accessed'] != 0 ? \Drupal::service('date.formatter')->format($user['last_accessed'], 'short') : t('never')]],
  318. ];
  319. }
  320. if (count($rows) > 0) {
  321. $header = [
  322. 'user' => t('User'),
  323. 'roles' => t('Roles'),
  324. 'created' => t('Created'),
  325. 'last_accessed' => t('Last accessed')
  326. ];
  327. $output['users_privileged'] = [
  328. '#type' => 'verbose_table_result',
  329. '#title' => t('All users with privileged access'),
  330. '#header' => $header,
  331. '#rows' => $rows,
  332. ];
  333. }
  334. // Verbose output for deleted users.
  335. $rows = [];
  336. foreach ($deleted_users as $user) {
  337. $rows[] = [
  338. 'user' => ['data' => ['#markup' => $user['name']]],
  339. 'roles' => ['data' => ['#markup' => $user['roles']]],
  340. 'created' => ['data' => ['#markup' => \Drupal::service('date.formatter')->format($user['created'], 'short')]],
  341. 'last_accessed' => ['data' => ['#markup' => $user['last_accessed'] != 0 ? \Drupal::service('date.formatter')->format($user['last_accessed'], 'short') : t('never')]],
  342. ];
  343. }
  344. if (count($rows) > 0) {
  345. $header = [
  346. 'user' => t('User'),
  347. 'roles' => t('Roles'),
  348. 'created' => t('Created'),
  349. 'last_accessed' => t('Last accessed')
  350. ];
  351. $output['deleted_users'] = [
  352. '#type' => 'verbose_table_result',
  353. '#title' => t('Deleted users with privileged access'),
  354. '#header' => $header,
  355. '#rows' => $rows,
  356. ];
  357. }
  358. // Show roles list with the permissions that are restricted for each.
  359. $roles_list = [];
  360. foreach (Role::loadMultiple($role_ids) as $role) {
  361. if (!$role->isAdmin()) {
  362. $restricted_permissions = array_intersect($this->restrictedPermissions, $role->getPermissions());
  363. $roles_list[] = $role->label() . ': ' . implode(", ", $restricted_permissions);
  364. }
  365. }
  366. $output['roles_list'] = [
  367. '#type' => 'fieldset',
  368. '#title' => t('List of roles with restricted permissions'),
  369. ['#type' => 'item', '#markup' => !empty($roles_list) ? implode('<br>', $roles_list) : t('None')],
  370. ];
  371. return $output;
  372. }
  373. /**
  374. * {@inheritdoc}
  375. */
  376. public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
  377. $form['reset_users'] = array(
  378. '#type' => 'submit',
  379. '#value' => t('Reset user data'),
  380. '#submit' => array(array($this, 'submitConfirmPrivilegedUsers')),
  381. );
  382. return $form;
  383. }
  384. /**
  385. * Resets current keyValue storage.
  386. *
  387. * @return array
  388. * Available users.
  389. */
  390. public function submitConfirmPrivilegedUsers(array $form, FormStateInterface $form_state) {
  391. \Drupal::keyValue('monitoring.users')->deleteAll();
  392. return $form;
  393. }
  394. }