account_sentinel.pages.inc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. <?php
  2. /**
  3. * @file
  4. * Contains pages, forms and callbacks for Account Sentinel.
  5. */
  6. /**
  7. * Generates the settings page.
  8. *
  9. * @return array
  10. * The settings form.
  11. */
  12. function account_sentinel_page_settings() {
  13. $roles = array();
  14. foreach (user_roles(TRUE) as $rid => $role) {
  15. $roles[$rid] = check_plain($role);
  16. }
  17. // Monitored roles.
  18. $form['account_sentinel_monitored_roles'] = array(
  19. '#type' => 'checkboxes',
  20. '#title' => t('Monitored roles'),
  21. '#description' => t("Mark which roles' accounts should be monitored."),
  22. '#options' => $roles,
  23. '#default_value' => account_sentinel_get_monitored_roles(),
  24. );
  25. // E-mail notification recipients.
  26. $form['account_sentinel_email_to'] = array(
  27. '#type' => 'textfield',
  28. '#title' => t('Recipient(s) of notification e-mails'),
  29. '#description' => t('Comma separated list of e-mail addresses to notify of changes to monitored accounts.'),
  30. '#default_value' => variable_get('account_sentinel_email_to', ''),
  31. );
  32. // Cron settings.
  33. $cron_key = account_sentinel_get_cron_key();
  34. $cron_url = url(
  35. 'system/account-sentinel-cron',
  36. array(
  37. 'absolute' => TRUE,
  38. 'query' => array('key' => $cron_key),
  39. )
  40. );
  41. $cron_reset_url = url(
  42. 'system/account-sentinel-reset-cron-key',
  43. array('query' => array('token' => drupal_get_token($cron_key)))
  44. );
  45. if (($cron_last = variable_get('account_sentinel_audit_last', 0)) != 0) {
  46. $cron_status = t('Last run: %audit-last ago.', array('%audit-last' => format_interval(REQUEST_TIME - $cron_last)));
  47. }
  48. else {
  49. $cron_status = t('The audit has not been run yet!');
  50. }
  51. $form['account_sentinel_cron_method'] = array(
  52. '#type' => 'radios',
  53. '#title' => t('Automatic DB check method'),
  54. '#description' => $cron_status,
  55. '#options' => array(
  56. 'drupal' => t("Run with Drupal's inbuilt cron"),
  57. 'custom' => t("Use Account Sentinel's own cron handler"),
  58. ),
  59. '#default_value' => variable_get('account_sentinel_cron_method', 'drupal'),
  60. );
  61. $form['account_sentinel_custom_cron_info'] = array(
  62. '#type' => 'container',
  63. '#states' => array(
  64. 'visible' => array(
  65. ':input[name="account_sentinel_cron_method"]' => array('value' => 'custom'),
  66. ),
  67. ),
  68. );
  69. $form['account_sentinel_custom_cron_info']['content'] = array(
  70. '#type' => 'markup',
  71. '#markup' => t(
  72. 'The custom cron URL is <a href="!cron_url" id="cron-link">!cron_url</a>.<br />You can request a new URL by clicking <a href="!cron_reset_url" id="cron-reset-link">here</a>. This will invalidate the current URL!',
  73. array(
  74. '!cron_url' => $cron_url,
  75. '!cron_reset_url' => $cron_reset_url,
  76. )
  77. ),
  78. );
  79. // Attach JavaScript.
  80. $form['#attached']['js'] = array(
  81. drupal_get_path('module', 'account_sentinel') . '/js/settings.js',
  82. );
  83. // Add submit handler.
  84. $form['#submit'][] = 'account_sentinel_page_settings_submit';
  85. return system_settings_form($form);
  86. }
  87. /**
  88. * Handles the settings form's validation.
  89. *
  90. * @param array $form
  91. * The form's structure.
  92. * @param array $form_state
  93. * The array containing the form's state.
  94. */
  95. function account_sentinel_page_settings_validate(array $form, array &$form_state) {
  96. // E-mail notification recipient validation.
  97. $to = $form_state['values']['account_sentinel_email_to'];
  98. $to_valid = TRUE;
  99. $addresses = explode(',', $to);
  100. foreach ($addresses as $address) {
  101. $address = trim($address);
  102. if (strpos($address, '<') !== FALSE) {
  103. $start = strpos($address, '<') + 1;
  104. $end = strpos($address, '>');
  105. if ($end === FALSE) {
  106. $to_valid = FALSE;
  107. break;
  108. }
  109. $address = substr($address, $start, $end - $start);
  110. }
  111. if ($address != '' && !valid_email_address($address)) {
  112. $to_valid = FALSE;
  113. break;
  114. }
  115. }
  116. if (!$to_valid) {
  117. form_set_error('account_sentinel_email_to', t('Invalid e-mail address!'));
  118. }
  119. }
  120. /**
  121. * Handles the settings form's submission.
  122. *
  123. * @param array $form
  124. * The form's structure.
  125. * @param array $form_state
  126. * The array containing the form's state.
  127. */
  128. function account_sentinel_page_settings_submit(array $form, array &$form_state) {
  129. // Only store IDs of monitored roles.
  130. $roles_new = &$form_state['values']['account_sentinel_monitored_roles'];
  131. $roles_new = array_keys(array_filter($roles_new));
  132. $roles_old = account_sentinel_get_monitored_roles();
  133. $roles_added = array_diff($roles_new, $roles_old);
  134. $roles_removed = array_diff($roles_old, $roles_new);
  135. // Check whether roles have changed.
  136. if (!empty($roles_added) || !empty($roles_removed)) {
  137. watchdog('account_sentinel', 'Monitored roles modified.');
  138. // Invoke snapshot integrity check.
  139. module_load_include('inc', 'account_sentinel', 'account_sentinel.audit');
  140. account_sentinel_audit_existence();
  141. account_sentinel_audit_integrity();
  142. // Delete unmonitored snapshots, create newly monitored users' snapshots.
  143. account_sentinel_rebuild_snapshots($roles_old, $roles_new);
  144. }
  145. }
  146. /**
  147. * Generates the report page.
  148. *
  149. * @return array
  150. * The report page.
  151. */
  152. function account_sentinel_page_report() {
  153. $filters = isset($_SESSION['account_sentinel_report_filter']) ? $_SESSION['account_sentinel_report_filter'] : array();
  154. $output = array();
  155. $output['account_sentinel_report_description'] = array(
  156. '#type' => 'markup',
  157. '#markup' => '<p>' . t(
  158. 'Here you can review the changes that were made to accounts monitored by Account Sentinel. To configure Account Sentinel <a href="!configure">click here</a>.',
  159. array('!configure' => url('admin/config/system/account-sentinel'))
  160. ) . '</p>',
  161. );
  162. $output['account_sentinel_report_filter'] = drupal_get_form('account_sentinel_page_report_filter_form');
  163. // Query the events.
  164. $events_per_page = 15;
  165. $fields = array(
  166. 'eid',
  167. 'uid',
  168. 'origin',
  169. 'type',
  170. 'data',
  171. 'by_uid',
  172. 'ip',
  173. 'timestamp',
  174. );
  175. $select = db_select('account_sentinel_logs', 'l')->extend('PagerDefault');
  176. $select->fields('l', $fields)
  177. ->orderBy('timestamp', 'DESC')
  178. ->orderBy('eid', 'DESC')
  179. ->limit($events_per_page);
  180. foreach ($filters as $filter => $filter_value) {
  181. if (in_array($filter, $fields)) {
  182. $select->condition($filter, $filter_value);
  183. }
  184. elseif ($filter == 'before') {
  185. $select->condition('timestamp', $filter_value, '<=');
  186. }
  187. elseif ($filter == 'after') {
  188. $select->condition('timestamp', $filter_value, '>=');
  189. }
  190. }
  191. $events_queried = $select->execute();
  192. // Fill the rows.
  193. $rows = array();
  194. while (($row = $events_queried->fetchAssoc()) !== FALSE) {
  195. // Format date.
  196. $row['timestamp'] = format_date($row['timestamp']);
  197. // Format account names.
  198. if ($row['origin'] == ACCOUNT_SENTINEL_EVENT_ORIGIN_DB_CHECK) {
  199. $row['by'] = '<strong>' . t('DB alteration') . '</strong>';
  200. }
  201. else {
  202. $row['by'] = theme('account_sentinel_username', array('uid' => $row['by_uid']));
  203. }
  204. $row['account'] = theme('account_sentinel_username', array('uid' => $row['uid']));
  205. // Add event message.
  206. $row['data'] = unserialize($row['data']);
  207. $row['message'] = account_sentinel_get_event_message($row['type'], $row['data']);
  208. // Add classes.
  209. $origin_class = 'origin--' . str_replace('_', '-', $row['origin']);
  210. $type_class = 'type--' . str_replace('_', '-', $row['type']);
  211. // Insert row.
  212. $rows[] = array(
  213. 'data' => array(
  214. $row['account'],
  215. $row['message'],
  216. $row['timestamp'],
  217. array(
  218. 'data' => $row['by'],
  219. 'class' => array('col--by'),
  220. ),
  221. $row['ip'],
  222. ),
  223. 'class' => array($type_class, $origin_class),
  224. );
  225. }
  226. $output['account_sentinel_report_table'] = array(
  227. '#theme' => 'table',
  228. '#header' => array(
  229. t('Account'),
  230. t('Event details'),
  231. t('Date'),
  232. t('By'),
  233. t('IP'),
  234. ),
  235. '#rows' => $rows,
  236. '#attributes' => array(
  237. 'class' => array('account-sentinel-table'),
  238. ),
  239. );
  240. $output['account_sentinel_report_pager'] = array('#theme' => 'pager');
  241. $output['#attached']['css'] = array(
  242. drupal_get_path('module', 'account_sentinel') . '/css/report.css',
  243. );
  244. return $output;
  245. }
  246. /**
  247. * Generates the filter form for the report page.
  248. *
  249. * @return array
  250. * The filter form.
  251. */
  252. function account_sentinel_page_report_filter_form() {
  253. $session = isset($_SESSION['account_sentinel_report_filter']) ? $_SESSION['account_sentinel_report_filter'] : array();
  254. $form['account_sentinel_filter'] = array(
  255. '#type' => 'fieldset',
  256. '#title' => t('Filter events'),
  257. '#collapsible' => TRUE,
  258. '#collapsed' => empty($session),
  259. );
  260. $filter = &$form['account_sentinel_filter'];
  261. $event_types = account_sentinel_event_type_strings();
  262. $origin_types = account_sentinel_event_origin_strings();
  263. $event_types = array_map('ucfirst', $event_types);
  264. $origin_types = array_map('ucfirst', $origin_types);
  265. asort($event_types);
  266. asort($origin_types);
  267. $weight = 0;
  268. $filter['fields'] = array(
  269. '#type' => 'container',
  270. '#attributes' => array('class' => array('account-sentinel-filter-fields')),
  271. );
  272. $filter['fields']['subject'] = array(
  273. '#type' => 'textfield',
  274. '#title' => t('Subject'),
  275. '#description' => t("The changed user's name, or alternatively their UID in \"#UID\" format."),
  276. '#size' => 20,
  277. '#maxlength' => 60,
  278. '#autocomplete_path' => 'user/autocomplete',
  279. '#default_value' => isset($session['subject']) ? $session['subject'] : '',
  280. '#weight' => $weight++,
  281. );
  282. $filter['fields']['ip'] = array(
  283. '#type' => 'textfield',
  284. '#title' => t('IP address'),
  285. '#description' => t('This field is irrelevant in cases when the database was modified.'),
  286. '#size' => 20,
  287. '#maxlength' => 45,
  288. '#default_value' => isset($session['ip']) ? $session['ip'] : '',
  289. '#weight' => $weight++,
  290. );
  291. $filter['fields']['origin'] = array(
  292. '#type' => 'select',
  293. '#multiple' => TRUE,
  294. '#title' => t('Event origin'),
  295. '#required' => FALSE,
  296. '#options' => $origin_types,
  297. '#size' => min(5, count($origin_types)),
  298. '#weight' => $weight++,
  299. '#default_value' => isset($session['origin']) ? $session['origin'] : array(),
  300. );
  301. $filter['fields']['type'] = array(
  302. '#type' => 'select',
  303. '#multiple' => TRUE,
  304. '#title' => t('Type of event'),
  305. '#required' => FALSE,
  306. '#options' => $event_types,
  307. '#size' => min(5, count($event_types)),
  308. '#weight' => $weight++,
  309. '#default_value' => isset($session['type']) ? $session['type'] : array(),
  310. );
  311. $filter['fields']['after'] = array(
  312. '#type' => 'date',
  313. '#title' => t('Occurred after'),
  314. '#required' => FALSE,
  315. '#weight' => $weight++,
  316. '#default_value' => isset($session['after']) ? account_sentinel_time_to_array($session['after']) : account_sentinel_time_to_array(0),
  317. );
  318. $filter['fields']['before'] = array(
  319. '#type' => 'date',
  320. '#title' => t('Occurred before'),
  321. '#required' => FALSE,
  322. '#weight' => $weight++,
  323. '#default_value' => isset($session['before']) ? account_sentinel_time_to_array($session['before']) : account_sentinel_time_to_array(time()),
  324. );
  325. $filter['actions'] = array(
  326. '#type' => 'actions',
  327. '#attributes' => array('class' => array('account-sentinel-filter-actions')),
  328. );
  329. $filter['actions']['submit'] = array(
  330. '#type' => 'submit',
  331. '#value' => (!empty($session) ? t('Refine') : t('Filter')),
  332. );
  333. if (!empty($session)) {
  334. $filter['actions']['reset'] = array(
  335. '#type' => 'submit',
  336. '#value' => t('Reset'),
  337. );
  338. }
  339. return $form;
  340. }
  341. /**
  342. * Converts a UNIX timestamp to a Form API date array.
  343. *
  344. * @param int $time
  345. * UNIX timestamp.
  346. *
  347. * @return array
  348. * Date array.
  349. */
  350. function account_sentinel_time_to_array($time = 0) {
  351. return array(
  352. 'year' => date('Y', $time),
  353. 'month' => date('m', $time),
  354. 'day' => date('d', $time),
  355. );
  356. }
  357. /**
  358. * Converts a Form API's date field's value to UNIX timestamp.
  359. *
  360. * @param array $date
  361. * Date array.
  362. * @param bool $end_of_day
  363. * If set to true, the last second of the day will be returned.
  364. *
  365. * @return int
  366. * UNIX timestamp.
  367. */
  368. function account_sentinel_array_to_time(array $date, $end_of_day = FALSE) {
  369. $time_of_day = $end_of_day ? '23:59:59' : '00:00:00';
  370. return strtotime($date['year'] . '-' . $date['month'] . '-' . $date['day'] . ' ' . $time_of_day);
  371. }
  372. /**
  373. * Handles the filter form's submission.
  374. *
  375. * @param array $form
  376. * The form's structure.
  377. * @param array $form_state
  378. * The array containing the form's state.
  379. */
  380. function account_sentinel_page_report_filter_form_submit(array $form, array &$form_state) {
  381. $session = &$_SESSION['account_sentinel_report_filter'];
  382. $values = $form_state['values'];
  383. switch ($values['op']) {
  384. case t('Refine'):
  385. case t('Filter'):
  386. if (($subject = trim($values['subject'])) != '') {
  387. $session['subject'] = $subject;
  388. $matches = array();
  389. if (preg_match('/\\#([0-9]+)/', $subject, $matches)) {
  390. // Store UID.
  391. $session['uid'] = $matches[1];
  392. }
  393. else {
  394. // Load by name.
  395. $user = user_load_by_name($subject);
  396. if ($user !== FALSE) {
  397. $session['uid'] = $user->uid;
  398. }
  399. }
  400. }
  401. if (($ip = trim($values['ip'])) != '') {
  402. $session['ip'] = $ip;
  403. }
  404. if (!empty($values['origin'])) {
  405. $session['origin'] = array_keys($values['origin']);
  406. }
  407. if (!empty($values['type'])) {
  408. $session['type'] = array_keys($values['type']);
  409. }
  410. if (!empty($values['after'])) {
  411. $session['after'] = account_sentinel_array_to_time($values['after']);
  412. }
  413. if (!empty($values['before'])) {
  414. $session['before'] = account_sentinel_array_to_time($values['before'], TRUE);
  415. }
  416. break;
  417. case t('Reset'):
  418. $session = array();
  419. break;
  420. }
  421. }
  422. /**
  423. * Handler for the module's own cron URL.
  424. */
  425. function account_sentinel_callback_cron() {
  426. // Only handle if the cron_method is set to custom.
  427. if (variable_get('account_sentinel_cron_method', 'drupal') == 'custom') {
  428. // Validate access.
  429. $query = drupal_get_query_parameters();
  430. $cron_key = account_sentinel_get_cron_key();
  431. if (isset($query['key']) && $query['key'] == $cron_key) {
  432. watchdog('account_sentinel', 'Invoked database audit from URL.');
  433. module_load_include('inc', 'account_sentinel', 'account_sentinel.audit');
  434. account_sentinel_audit();
  435. }
  436. else {
  437. watchdog('account_sentinel', 'Invalid access to audit URL.', array(), WATCHDOG_WARNING);
  438. }
  439. }
  440. }
  441. /**
  442. * Handler for the cron key resetting URL.
  443. */
  444. function account_sentinel_callback_reset_cron_key() {
  445. $output = array();
  446. // Validate access.
  447. $query = drupal_get_query_parameters();
  448. $cron_key = account_sentinel_get_cron_key();
  449. if (isset($query['token']) && drupal_valid_token($query['token'], $cron_key)) {
  450. // Reset cron key.
  451. $cron_key = account_sentinel_reset_cron_key();
  452. // Return the new URL.
  453. $output['url_cron'] = url(
  454. 'system/account-sentinel-cron',
  455. array(
  456. 'absolute' => TRUE,
  457. 'query' => array('key' => $cron_key),
  458. )
  459. );
  460. $output['url_reset'] = url(
  461. 'system/account-sentinel-reset-cron-key',
  462. array(
  463. 'query' => array('token' => drupal_get_token($cron_key)),
  464. )
  465. );
  466. }
  467. // Output JSON if JavaScript is enabled, otherwise refresh the page.
  468. if (isset($query['js'])) {
  469. drupal_json_output($output);
  470. }
  471. else {
  472. drupal_goto('admin/config/system/account-sentinel');
  473. }
  474. }