account_sentinel.audit.inc 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <?php
  2. /**
  3. * @file
  4. * Contains functions for the automatic database check.
  5. */
  6. /**
  7. * Runs a full database audit.
  8. */
  9. function account_sentinel_audit() {
  10. account_sentinel_audit_existence();
  11. account_sentinel_audit_integrity();
  12. account_sentinel_audit_changes();
  13. watchdog('account_sentinel', 'Ran database audit.');
  14. variable_set('account_sentinel_audit_last', REQUEST_TIME);
  15. }
  16. /**
  17. * Checks whether stored snapshots are valid.
  18. */
  19. function account_sentinel_audit_integrity() {
  20. $hash_key = drupal_get_hash_salt();
  21. $events = array();
  22. // Check integrity in account_sentinel_users.
  23. $query = db_select('account_sentinel_users', 'u')
  24. ->fields('u', array('uid', 'name', 'pass', 'mail', 'status', 'checksum'));
  25. $query->where('checksum <> sha2(concat(u.uid, u.name, u.pass, u.mail, u.status, :hash_key), 384)', array(':hash_key' => $hash_key));
  26. $result = $query->execute();
  27. while (($row = $result->fetchAssoc()) !== FALSE) {
  28. $events[$row['uid']][] = array(
  29. 'type' => ACCOUNT_SENTINEL_EVENT_TYPE_SNAPSHOT_INVALID,
  30. 'data' => array(
  31. 'table' => 'users',
  32. 'row' => $row,
  33. ),
  34. );
  35. }
  36. // Check integrity in account_sentinel_users_roles.
  37. $query = db_select('account_sentinel_users_roles', 'ur')
  38. ->fields('ur', array('uid', 'rid', 'checksum'));
  39. $query->where('checksum <> sha2(concat(ur.uid, ur.rid, :hash_key), 384)', array(':hash_key' => $hash_key));
  40. $result = $query->execute();
  41. while (($row = $result->fetchAssoc()) !== FALSE) {
  42. $events[$row['uid']][] = array(
  43. 'type' => ACCOUNT_SENTINEL_EVENT_TYPE_SNAPSHOT_INVALID,
  44. 'data' => array(
  45. 'table' => 'users_roles',
  46. 'row' => $row,
  47. ),
  48. );
  49. }
  50. foreach ($events as $uid => $event_list) {
  51. account_sentinel_record_events($uid, ACCOUNT_SENTINEL_EVENT_ORIGIN_DB_CHECK, $event_list);
  52. $user = user_load($uid, TRUE);
  53. if ($user !== FALSE) {
  54. $user = account_sentinel_monitored_account_data($user);
  55. account_sentinel_update_snapshot($user);
  56. }
  57. }
  58. }
  59. /**
  60. * Checks whether trusted users are the same as the users who have snapshots.
  61. */
  62. function account_sentinel_audit_existence() {
  63. $monitored_roles = account_sentinel_get_monitored_roles();
  64. if (!empty($monitored_roles)) {
  65. // Check for missing snapshots.
  66. $missing = db_select('users', 'u');
  67. $missing->fields('u', array('uid'));
  68. $missing->rightJoin('users_roles', 'ur', 'u.uid = ur.uid');
  69. $missing->condition('ur.rid', $monitored_roles);
  70. $missing->leftJoin('account_sentinel_users', 'asu', 'u.uid = asu.uid');
  71. $missing->fields('asu', array('uid'));
  72. $missing->condition('asu.uid', NULL);
  73. $result = $missing->execute();
  74. while (($row = $result->fetchAssoc()) !== FALSE) {
  75. $uid = $row['uid'];
  76. // UID can be NULL if users_roles contains roles of a forcibly deleted
  77. // user. That case will be handled in the second part of the function, so
  78. // we only work with existing users now.
  79. if (is_numeric($uid)) {
  80. $changes = array();
  81. $changes[] = array(
  82. 'type' => ACCOUNT_SENTINEL_EVENT_TYPE_SNAPSHOT_MISSING,
  83. 'data' => array(),
  84. );
  85. // A missing snapshot means that the user was granted one or more
  86. // monitored permissions, so it's also an event type of ROLE_ADD.
  87. // Get the roles of the user.
  88. $user = user_load($uid);
  89. if ($user !== FALSE) {
  90. $roles = array_intersect($monitored_roles, array_keys($user->roles));
  91. // Current roles.
  92. $new = array(
  93. 'uid' => $uid,
  94. 'roles' => $roles,
  95. );
  96. // Roles previously stored as snapshots.
  97. $select_original = db_select('account_sentinel_users_roles', 'asur');
  98. $select_original->fields('asur', array('rid'))
  99. ->condition('uid', $uid);
  100. $original_roles_queried = $select_original->execute();
  101. $original_roles = $original_roles_queried->fetchAllAssoc('rid');
  102. $original = array(
  103. 'uid' => $uid,
  104. 'roles' => array_keys($original_roles),
  105. );
  106. $changes = array_merge(
  107. $changes,
  108. account_sentinel_detect_changes($new, $original)
  109. );
  110. }
  111. account_sentinel_record_events($uid, ACCOUNT_SENTINEL_EVENT_ORIGIN_DB_CHECK, $changes);
  112. if ($user !== FALSE) {
  113. $user = account_sentinel_monitored_account_data($user);
  114. account_sentinel_update_snapshot($user);
  115. }
  116. }
  117. }
  118. }
  119. // Check for excess snapshots.
  120. // Revoked roles are detected in account_sentinel_audit_changes(), so we only
  121. // look for missing rows in the users table.
  122. $excess = db_select('users', 'u');
  123. $excess->fields('asu', array('uid', 'name', 'mail'))
  124. ->condition('u.uid', NULL)
  125. ->rightJoin('account_sentinel_users', 'asu', 'u.uid = asu.uid');
  126. $result = $excess->execute();
  127. while (($row = $result->fetchAssoc()) !== FALSE) {
  128. $changes = array(
  129. array(
  130. 'type' => ACCOUNT_SENTINEL_EVENT_TYPE_USER_DELETE,
  131. 'data' => array(
  132. 'uid' => $row['uid'],
  133. 'name' => $row['name'],
  134. 'mail' => $row['mail'],
  135. ),
  136. ),
  137. );
  138. account_sentinel_record_events($row['uid'], ACCOUNT_SENTINEL_EVENT_ORIGIN_DB_CHECK, $changes);
  139. account_sentinel_delete_snapshot($row['uid']);
  140. }
  141. }
  142. /**
  143. * Scans the database for manual changes in users and users_roles.
  144. *
  145. * Compares the monitored users' actual data to Account Sentinel's stored
  146. * snapshots.
  147. */
  148. function account_sentinel_audit_changes() {
  149. $accounts = array();
  150. $monitored_roles = account_sentinel_get_monitored_roles();
  151. // Look for changes in user details.
  152. $query = db_select('users', 'u');
  153. $query->fields('u', array('uid', 'name', 'pass', 'mail', 'status'))
  154. ->fields('asu', array('name', 'pass', 'mail', 'status'))
  155. ->where('u.name <> asu.name OR u.pass <> asu.pass OR u.mail <> asu.mail OR u.status <> asu.status')
  156. ->rightJoin('account_sentinel_users', 'asu', 'u.uid = asu.uid');
  157. $result = $query->execute();
  158. while (($row = $result->fetchAssoc()) !== FALSE) {
  159. $accounts[$row['uid']] = array(
  160. 'new' => array(
  161. 'name' => $row['name'],
  162. 'pass' => $row['pass'],
  163. 'mail' => $row['mail'],
  164. 'status' => $row['status'],
  165. ),
  166. 'original' => array(
  167. 'name' => $row['asu_name'],
  168. 'pass' => $row['asu_pass'],
  169. 'mail' => $row['asu_mail'],
  170. 'status' => $row['asu_status'],
  171. ),
  172. );
  173. }
  174. if (!empty($monitored_roles)) {
  175. // Look for added user roles.
  176. $added = db_select('users_roles', 'ur');
  177. $added->fields('ur', array('uid', 'rid'))
  178. ->condition('ur.rid', $monitored_roles)
  179. ->notExists(
  180. db_select('account_sentinel_users_roles', 'asur')
  181. ->fields('asur', array())
  182. ->where('ur.rid = asur.rid')
  183. );
  184. $added_roles = $added->execute();
  185. while (($row = $added_roles->fetchAssoc()) !== FALSE) {
  186. $accounts[$row['uid']]['new']['roles'][] = $row['rid'];
  187. if (!isset($accounts[$row['uid']]['original']['roles'])) {
  188. $accounts[$row['uid']]['original']['roles'] = array();
  189. }
  190. }
  191. }
  192. // Look for removed user roles.
  193. $removed = db_select('account_sentinel_users_roles', 'asur');
  194. $removed->fields('asur', array('uid', 'rid'))
  195. ->notExists(
  196. db_select('users_roles', 'ur')
  197. ->fields('ur', array())
  198. ->where('ur.rid = asur.rid')
  199. );
  200. $removed_roles = $removed->execute();
  201. while (($row = $removed_roles->fetchAssoc()) !== FALSE) {
  202. $accounts[$row['uid']]['original']['roles'][] = $row['rid'];
  203. if (!isset($accounts[$row['uid']]['new']['roles'])) {
  204. $accounts[$row['uid']]['new']['roles'] = array();
  205. }
  206. }
  207. // Evaluate changes.
  208. foreach ($accounts as $uid => $account_states) {
  209. $account_states['new']['uid'] = $uid;
  210. $account_states['original']['uid'] = $uid;
  211. $changes = account_sentinel_detect_changes(
  212. $account_states['new'],
  213. $account_states['original']
  214. );
  215. account_sentinel_record_events($uid, ACCOUNT_SENTINEL_EVENT_ORIGIN_DB_CHECK, $changes);
  216. $user = user_load($uid, TRUE);
  217. if ($user !== FALSE) {
  218. $user = account_sentinel_monitored_account_data($user);
  219. account_sentinel_update_snapshot($user);
  220. }
  221. }
  222. }