adb.module 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. <?php
  2. define('ADB_DEVEL_MIN_TEXTAREA', 50);
  3. /**
  4. * Implements hook_menu().
  5. */
  6. function adb_menu() {
  7. $items['admin/reports/access-denied/backtrace'] = array(
  8. 'title' => "Last access denied errors backtrace",
  9. 'description' => "View 'access denied' errors backtrace (403s).",
  10. 'page callback' => 'adb_last',
  11. 'access arguments' => array('access site reports'),
  12. );
  13. $items['admin/reports/event/backtrace/%'] = array(
  14. 'title' => 'Details',
  15. 'page callback' => 'adb_event',
  16. 'page arguments' => array(4),
  17. 'access arguments' => array('access site reports'),
  18. );
  19. $items['admin/config/development/access-denied-backtrace/configure'] = array(
  20. 'title' => 'Access denied backtrace settings',
  21. 'page callback' => 'drupal_get_form',
  22. 'page arguments' => array('adb_settings'),
  23. 'access arguments' => array('administer access denied backtrace'),
  24. );
  25. return $items;
  26. }
  27. /**
  28. * Implements hook_permission().
  29. */
  30. function adb_permission() {
  31. return array(
  32. 'administer access denied backtrace' => array(
  33. 'title' => t('administer access denied backtrace'),
  34. 'description' => t('Change which roles are enabled to record access denied backtrace using the admin interface.'),
  35. ),
  36. );
  37. } // fontyourface_perm
  38. /**
  39. * Generate a adb settings form.
  40. *
  41. * @ingroup forms
  42. * @see filter_admin_format_form_validate()
  43. * @see filter_admin_format_form_submit()
  44. */
  45. function adb_settings($form, &$form_state) {
  46. // Add user role access selection.
  47. $form['adb_roles'] = array(
  48. '#type' => 'checkboxes',
  49. '#title' => t('Roles'),
  50. '#options' => array_map('check_plain', user_roles()),
  51. '#default_value' => variable_get('adb_roles',array()),
  52. '#description' => t('Configure what roles will be able to record access denied backtrace'),
  53. );
  54. return system_settings_form($form);
  55. }
  56. /**
  57. * Menu callback; displays details about access denied backtrace log message.
  58. */
  59. function adb_event($id) {
  60. $severity = watchdog_severity_levels();
  61. $result = db_query('SELECT adb.*, u.name, u.uid FROM {adb} adb INNER JOIN {users} u ON adb.uid = u.uid WHERE adb.adbid = :id', array(':id' => $id))->fetchObject();
  62. if ($dblog = $result) {
  63. $rows = array(
  64. array(
  65. array('data' => t('Date'), 'header' => TRUE),
  66. format_date($dblog->timestamp, 'long'),
  67. ),
  68. array(
  69. array('data' => t('User'), 'header' => TRUE),
  70. theme('username', array('account' => $dblog)),
  71. ),
  72. array(
  73. array('data' => t('Location'), 'header' => TRUE),
  74. l($dblog->location, $dblog->location),
  75. ),
  76. array(
  77. array('data' => t('User permissions'), 'header' => TRUE),
  78. $dblog->permissions,
  79. ),
  80. array(
  81. array('data' => t('User role permissions'), 'header' => TRUE),
  82. $dblog->role_permissions,
  83. ),
  84. array(
  85. array('data' => t('Node access denied'), 'header' => TRUE),
  86. $dblog->node_access_denied,
  87. ),
  88. array(
  89. array('data' => t('Backtrace'), 'header' => TRUE),
  90. theme('adb_message', array('event' => $dblog)),
  91. ),
  92. );
  93. $build['dblog_table'] = array(
  94. '#theme' => 'table',
  95. '#rows' => $rows,
  96. '#attributes' => array('class' => array('dblog-event')),
  97. );
  98. return $build;
  99. }
  100. else {
  101. return '';
  102. }
  103. }
  104. /**
  105. * Implements hook_theme().
  106. */
  107. function adb_theme() {
  108. return array(
  109. 'adb_message' => array(
  110. 'variables' => array('event' => NULL),
  111. ),
  112. );
  113. }
  114. /**
  115. * Returns HTML for a log message.
  116. *
  117. * @param $variables
  118. * An associative array containing:
  119. * - event: An object with at least the message and variables properties.
  120. * - link: (optional) Format message as link, event->wid is required.
  121. *
  122. * @ingroup themeable
  123. */
  124. function theme_adb_message($variables) {
  125. $event = $variables['event'];
  126. $link = (isset($variables['link']))?$variables['link']:FALSE;
  127. $output = $event->backtrace;
  128. // Truncate message to 56 chars.
  129. if($link) {
  130. $output = truncate_utf8(filter_xss($output, array()), 56, TRUE, TRUE);
  131. $output = l($output, 'admin/reports/event/backtrace/' . $event->adbid, array('html' => TRUE));
  132. }
  133. return $output;
  134. }
  135. /**
  136. * Menu callback; generic function to display a page of the last access denied
  137. * backtrace.
  138. *
  139. * Messages are not truncated because events from this page have no detail view.
  140. *
  141. */
  142. function adb_last() {
  143. $rows = array();
  144. $build['dblog_clear_log_form'] = drupal_get_form('adb_clear_log_form');
  145. $header = array(
  146. array('data' => t('Date'), 'field' => 'adb.adbid', 'sort' => 'desc'),
  147. t('Backtrace'),
  148. array('data' => t('User'), 'field' => 'u.name'),
  149. );
  150. $query = db_select('adb', 'adb')->extend('PagerDefault')->extend('TableSort');
  151. $query->leftJoin('users', 'u', 'adb.uid = u.uid');
  152. $query
  153. ->fields('adb', array('adbid', 'uid', 'timestamp', 'backtrace'))
  154. ->addField('u', 'name');
  155. $result = $query
  156. ->limit(50)
  157. ->orderByHeader($header)
  158. ->execute();
  159. foreach ($result as $dblog) {
  160. $rows[] = array('data' =>
  161. array(
  162. // Cells
  163. format_date($dblog->timestamp, 'short'),
  164. theme('adb_message', array('event' => $dblog, 'link' => TRUE)),
  165. theme('username', array('account' => $dblog)),
  166. ),
  167. // Attributes for tr
  168. 'class' => array(drupal_html_class('adb')),
  169. );
  170. }
  171. $build['dblog_table'] = array(
  172. '#theme' => 'table',
  173. '#header' => $header,
  174. '#rows' => $rows,
  175. '#attributes' => array('id' => 'admin-dblog'),
  176. '#empty' => t('No access denied bractrace log available.'),
  177. );
  178. $build['dblog_pager'] = array('#theme' => 'pager');
  179. return $build;
  180. }
  181. /**
  182. * Invokes a hook in all enabled modules that implement it.
  183. *
  184. * @param $hook
  185. * The name of the hook to invoke.
  186. * @param ...
  187. * Arguments to pass to the hook.
  188. *
  189. * @return
  190. * An array of return values of the hook implementations. If modules return
  191. * arrays from their implementations, those are merged into one array.
  192. */
  193. function adb_validate_module_access($node, $op, $account) {
  194. $args = array($node, $op, $account);
  195. $hook = 'node_access';
  196. $return = array();
  197. foreach (module_implements($hook) as $module) {
  198. $function = $module . '_' . $hook;
  199. if (function_exists($function)) {
  200. $result = call_user_func_array($function, $args);
  201. if (isset($result) && is_array($result)) {
  202. foreach ($result as $subaccess) {
  203. if ($subaccess == NODE_ACCESS_DENY) {
  204. $return[] = $module;
  205. break;
  206. }
  207. }
  208. $return = array_merge_recursive($return, $result);
  209. } elseif (isset($result) && $result == NODE_ACCESS_DENY) {
  210. $return[] = $module;
  211. }
  212. }
  213. }
  214. return t('Modules denying') . " " . implode(',', $return);
  215. }
  216. /**
  217. * Implements hook_watchdog().
  218. *
  219. * If an 'access denied' error is logged and user role is enabled for debug, the
  220. * execution trace is stored to try to fix what is wrong.
  221. */
  222. function adb_watchdog($log_entry) {
  223. $account = user_load($log_entry['uid']);
  224. $adb_roles = array_filter(variable_get('adb_roles',array()));
  225. $valid_role = array_intersect_key($adb_roles,$log_entry['user']->roles);
  226. $account_rights = '';
  227. $rights = drupal_static('node_access', array());
  228. $permissions_denied = '';
  229. if (isset($rights[$log_entry['uid']])) {
  230. $account_rights = $rights[$log_entry['uid']];
  231. foreach ($account_rights as $node => $rights) {
  232. foreach ($rights as $action => $access) {
  233. if (!$access) {
  234. $permissions_denied .= $action . ' ' . $node . ". ";
  235. $permissions_denied .= adb_validate_module_access($node, $action, $log_entry['user']) . ".<br/>";
  236. }
  237. }
  238. }
  239. }
  240. else {
  241. // Common message for annonymous users.
  242. $permissions_denied = t('There are not explicit access denied for this user');
  243. }
  244. $user_access = drupal_static('user_access', array());
  245. $role_permissions = user_role_permissions($account->roles);
  246. //Validate if entry is access denied and current user belog to enabled role to
  247. //record backtrace
  248. if (!empty($valid_role) && $log_entry['type'] == 'access denied' ) {
  249. $backtrace = _ddebug_backtrace(TRUE);
  250. // Pop the stack up to the drupal_access_denied() call.
  251. for ($i = 0; $i < 5; ++$i) {
  252. array_shift($backtrace);
  253. }
  254. $user_role_permissions = (isset($role_permissions[$log_entry['uid']])?$role_permissions[$log_entry['uid']]:array());
  255. $user_access_permissions = (isset($user_access[$log_entry['uid']]))?$user_access[$log_entry['uid']]:array();
  256. /* print_r($user_access[$log_entry['uid']]);
  257. print_r($user_role_permissions);*/
  258. Database::getConnection('default', 'default')->insert('adb')
  259. ->fields(array(
  260. 'uid' => $log_entry['uid'],
  261. 'node_access_denied' => $permissions_denied,
  262. 'permissions' => _dprint_r($user_access_permissions, TRUE),
  263. 'role_permissions' => _dprint_r($user_role_permissions, TRUE),
  264. 'backtrace' => _dprint_r($backtrace, TRUE),
  265. 'location' => $log_entry['request_uri'],
  266. 'timestamp' => $log_entry['timestamp'],
  267. ))
  268. ->execute();
  269. }
  270. }
  271. /**
  272. * Return form for dblog clear button.
  273. *
  274. * @ingroup forms
  275. * @see dblog_clear_log_submit()
  276. */
  277. function adb_clear_log_form($form) {
  278. $form['adb_clear'] = array(
  279. '#type' => 'fieldset',
  280. '#title' => t('Clear access denied backtraces'),
  281. '#description' => t('This will permanently remove the access denied backtraces from the database.'),
  282. '#collapsible' => TRUE,
  283. '#collapsed' => TRUE,
  284. );
  285. $form['adb_clear']['clear'] = array(
  286. '#type' => 'submit',
  287. '#value' => t('Clear access denied backtraces'),
  288. '#submit' => array('adb_clear_log_submit'),
  289. );
  290. return $form;
  291. }
  292. /**
  293. * Submit callback: clear database with log messages.
  294. */
  295. function adb_clear_log_submit() {
  296. db_delete('adb')->execute();
  297. drupal_set_message(t('Database access denied backtrace cleared.'));
  298. }
  299. /**
  300. * Pretty-print a variable to the browser (no krumo).
  301. * Displays only for users with proper permissions. If
  302. * you want a string returned instead of a print, use the 2nd param.
  303. * based in devel function dprint_r
  304. */
  305. function _dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check = TRUE) {
  306. if ($name) {
  307. $name .= ' => ';
  308. }
  309. if ($function == 'drupal_var_export') {
  310. include_once DRUPAL_ROOT . '/includes/utility.inc';
  311. $output = drupal_var_export($input);
  312. } else {
  313. ob_start();
  314. $function($input);
  315. $output = ob_get_clean();
  316. }
  317. if ($check) {
  318. $output = check_plain($output);
  319. }
  320. if (count($input, COUNT_RECURSIVE) > ADB_DEVEL_MIN_TEXTAREA) {
  321. // don't use fapi here because sometimes fapi will not be loaded
  322. $printed_value = "<textarea rows=30 style=\"width: 100%;\">\n" . $name . $output . '</textarea>';
  323. } else {
  324. $printed_value = '<pre>' . $name . $output . '</pre>';
  325. }
  326. if ($return) {
  327. return $printed_value;
  328. } else {
  329. print $printed_value;
  330. }
  331. }
  332. /**
  333. * Print the function call stack.
  334. * copied from devel module but with access for all roles
  335. */
  336. function _ddebug_backtrace($return = FALSE, $pop = 0) {
  337. $backtrace = debug_backtrace();
  338. while ($pop-- > 0) {
  339. array_shift($backtrace);
  340. }
  341. $counter = count($backtrace);
  342. $path = $backtrace[$counter - 1]['file'];
  343. $path = substr($path, 0, strlen($path) - 10);
  344. $paths[$path] = strlen($path) + 1;
  345. $paths[DRUPAL_ROOT] = strlen(DRUPAL_ROOT) + 1;
  346. $nbsp = "\xC2\xA0";
  347. // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher.
  348. // (This is Drupal's error_level, which is different from $error_level,
  349. // and we purposely ignore the difference between _SOME and _ALL,
  350. // see #970688!)
  351. if (variable_get('error_level', 1) >= 1) {
  352. while (!empty($backtrace)) {
  353. $call = array();
  354. if (isset($backtrace[0]['file'])) {
  355. $call['file'] = $backtrace[0]['file'];
  356. foreach ($paths as $path => $len) {
  357. if (strpos($backtrace[0]['file'], $path) === 0) {
  358. $call['file'] = substr($backtrace[0]['file'], $len);
  359. }
  360. }
  361. $call['file'] .= ':' . $backtrace[0]['line'];
  362. }
  363. if (isset($backtrace[1])) {
  364. if (isset($backtrace[1]['class'])) {
  365. $function = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
  366. } else {
  367. $function = $backtrace[1]['function'] . '()';
  368. }
  369. $call['args'] = $backtrace[1]['args'];
  370. } else {
  371. $function = 'main()';
  372. $call['args'] = $_GET;
  373. }
  374. $nicetrace[($counter <= 10 ? $nbsp : '') . --$counter . ': ' . $function] = $call;
  375. array_shift($backtrace);
  376. }
  377. if ($return) {
  378. return $nicetrace;
  379. }
  380. kprint_r($nicetrace);
  381. }
  382. }