Projet

Général

Profil

Paste
Télécharger (19,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / security_review / security_review.pages.inc @ bb746689

1
<?php
2

    
3
/**
4
 * @file security_review.pages.inc
5
 */
6

    
7
/**
8
 * Page callback for run & review.
9
 */
10
function security_review_page() {
11
  $checks = array();
12
  $output = array();
13
  // Retrieve the checklist.
14
  module_load_include('inc', 'security_review');
15
  $checklist = security_review_get_checklist();
16
  // Retrieve results from last run of the checklist.
17
  $checks = security_review_get_stored_results();
18
  // Only users with the proper permission can run the checklist.
19
  if (user_access('run security checks')) {
20
    $output += drupal_get_form('security_review_run_form', $checks);
21
  }
22

    
23
  if (!empty($checks)) {
24
    // We have prior results, so display them.
25
    $output['results'] = security_review_reviewed($checklist, $checks);
26
  }
27
  else {
28
    // If they haven't configured the site, prompt them to do so.
29
    $variable = variable_get('security_review_log', FALSE);
30
    if (!$variable) {
31
      drupal_set_message(t('It appears this is your first time using the Security Review checklist. Before running the checklist please review the settings page at !link to set which roles are untrusted.', array('!link' => l('admin/reports/security-review/settings', 'admin/reports/security-review/settings'))));
32
    }
33
  }
34
  return $output;
35
}
36

    
37
function security_review_reviewed($checklist, $checks, $namespace = NULL) {
38
  $items = array();
39
  $last_run = variable_get('security_review_last_run', '');
40
  $date = !empty($last_run) ? format_date($last_run) : '';
41
  $header = t('Review results from last run !date', array('!date' => $date));
42
  $desc = t("Here you can review the results from the last run of the checklist. Checks are not always perfectly correct in their procedure and result. You can keep a check from running by clicking the 'Skip' link beside it. You can run the checklist again by expanding the fieldset above.");
43
  foreach ($checks as $check) {
44
    // Skip this iteration if the result has no matching item in the checklist.
45
    if (!isset($checklist[$check['namespace']][$check['reviewcheck']])) {
46
      continue;
47
    }
48
    $message = $check['result'] ? $checklist[$check['namespace']][$check['reviewcheck']]['success'] : $checklist[$check['namespace']][$check['reviewcheck']]['failure'];
49
    $title = $check['result'] ? t('OK') : t('Error');
50
    $class = $check['skip'] ? 'info' : ($check['result'] ? 'ok' : 'error');
51
    $toggle = $check['skip'] ? t('Enable') : t('Skip');
52
    $token = drupal_get_token($check['reviewcheck']);
53
    $link_options = array(
54
      'query' => array('token' => $token),
55
      //'attributes' => array('class' => 'use-ajax'),
56
    );
57
    $items[] = array(
58
      'title' => $title,
59
      'value' => $check['result'],
60
      'class' => $class,
61
      'message' => $message,
62
      'help_link' => l(t('Details'), 'admin/reports/security-review/help/' . $check['namespace'] . '/' . $check['reviewcheck']),
63
      'toggle_link' => l($toggle, 'admin/reports/security-review/toggle/nojs/' . $check['reviewcheck'], $link_options),
64
    );
65
  }
66
  $output = theme('security_review_reviewed', array('items' => $items, 'header' => $header, 'description' => $desc));
67
  // @todo #markup?
68
  return array('#markup' => $output);
69
}
70

    
71
function security_review_run_form($form, &$form_state, $checks = NULL) {
72
  $form['run_form'] = array(
73
    '#type' => 'fieldset',
74
    '#title' => t('Run'),
75
    '#description' => t('Click the button below to run the security checklist and review the results.'),
76
    '#collapsible' => TRUE,
77
    '#collapsed' => empty($checks) ? FALSE : TRUE,
78
  );
79
  $form['run_form']['submit'] = array(
80
    '#type' => 'submit',
81
    '#value' => t('Run checklist'),
82
  );
83

    
84
  return $form;
85
}
86

    
87
function security_review_run_form_submit($form, &$form_state) {
88
  module_load_include('inc', 'security_review');
89
  $checklist = security_review_get_checklist();
90
  $skipped = security_review_skipped_checks();
91
  // Remove checks that are being skipped.
92
  if (!empty($skipped)) {
93
    foreach ($skipped as $module => $checks) {
94
      foreach ($checks as $check_name => $check) {
95
        unset($checklist[$module][$check_name]);
96
      }
97
      if (empty($checklist[$module])) {
98
        unset($checklist[$module]);
99
      }
100
    }
101
  }
102

    
103
  // Use Batch to process the checklist.
104
  $batch = array('operations' => array(),
105
    'title' => t('Performing Security Review'),
106
    'progress_message' => t('Progress @current out of @total.'),
107
    'error_message' => t('An error occurred. Rerun the process or consult the logs.'),
108
    'finished' => '_security_review_batch_finished',
109
  );
110
  $log = variable_get('security_review_log', TRUE);
111
  foreach ($checklist as $module => $checks) {
112
    foreach ($checks as $check_name => $check) {
113
      // Each check is its own operation. There could be a case where a single
114
      // check needs to run itself a batch operation, perhaps @todo?
115
      $batch['operations'][] = array('_security_review_batch_op', array($module, $check_name, $check, $log));
116
    }
117
  }
118
  batch_set($batch);
119
  return;
120
}
121

    
122
/**
123
 * Module settings form.
124
 */
125
function security_review_settings() {
126
  module_load_include('inc', 'security_review');
127
  $checklist = security_review_get_checklist();
128
  $roles = user_roles();
129
  foreach ($roles as $rid => $role) {
130
    $options[$rid] = check_plain($role);
131
  }
132
  $message = '';
133
  $defaults = _security_review_default_untrusted_roles();
134
  if (array_key_exists(DRUPAL_AUTHENTICATED_RID, $defaults)) {
135
    $message = 'You have allowed anonymous users to create accounts without approval so the authenticated role defaults to untrusted.';
136
  }
137

    
138
  $form['security_review_untrusted_roles'] = array(
139
    '#type' => 'checkboxes',
140
    '#title' => t('Untrusted roles'),
141
    '#description' => t('Mark which roles are not trusted. The anonymous role defaults to untrusted. @message Read more about the idea behind trusted and untrusted roles on <a href="!url">DrupalScout.com</a>. Most Security Review checks look for resources usable by untrusted roles.', array('@message' => $message, '!url' => url('http://drupalscout.com/knowledge-base/importance-user-roles-and-permissions-site-security'))),
142
    '#options' => $options,
143
    '#default_value' => variable_get('security_review_untrusted_roles', array_keys($defaults)),
144
  );
145

    
146
  $inactive_namespaces = array();
147
  // Report stored checks that aren't currently active.
148
  $checks = security_review_get_stored_results();
149
  foreach ($checks as $check) {
150
    if (!isset($checklist[$check['namespace']][$check['reviewcheck']])) {
151
      $inactive_namespaces[] = $check['namespace'];
152
    }
153
  }
154
  if (!empty($inactive_namespaces)) {
155
    $inactive_checks = implode(', ', $inactive_namespaces);
156
    $form['inactive_checks'] = array(
157
      '#prefix' => '<div class="messages warning">',
158
      '#suffix' => '</div>',
159
      '#markup' => t('Inactive checks are being stored under namespaces: %modules. Enabling associated modules may allow these checks to be run again. Inactive checks must be manually removed or uninstall and reinstall Security Review to clear all stored checks.', array('%modules' => $inactive_checks))
160
    );
161
  }
162

    
163
  $form['security_review_adv'] = array(
164
    '#type' => 'fieldset',
165
    '#title' => t('Advanced'),
166
    '#collapsible' => TRUE,
167
    '#collapsed' => FALSE,
168
  );
169
  $form['security_review_adv']['security_review_log'] = array(
170
    '#type' => 'checkbox',
171
    '#title' => t('Log checklist results and skips'),
172
    '#description' => t('The result of each check and skip can be logged to watchdog for tracking.'),
173
    '#default_value' => variable_get('security_review_log', TRUE),
174
  );
175
  $options = $values = array();
176

    
177
  $skipped = security_review_skipped_checks();
178
  foreach ($checklist as $module => $checks) {
179
    foreach ($checks as $check_name => $check) {
180
      // Determine if check is being skipped.
181
      if (!empty($skipped) && isset($skipped[$module]) && array_key_exists($check_name, $skipped[$module])) {
182
        $values[] = $check_name;
183
        $label = t('!name <em>skipped by UID !uid on !date</em>', array('!name' => $check['title'], '!uid' => $skipped[$module][$check_name]['skipuid'], '!date' => format_date($skipped[$module][$check_name]['skiptime'])));
184
      }
185
      else {
186
        $label = $check['title'];
187
      }
188
      $options[$check_name] = $label;
189
    }
190
  }
191
  $form['security_review_adv']['security_review_skip'] = array(
192
    '#type' => 'checkboxes',
193
    '#title' => t('Checks to skip'),
194
    '#description' => t('Skip running certain checks. This can also be set on the <em>Run & review</em> page. It is recommended that you do not skip any checks unless you know the result is wrong or the process times out while running.'),
195
    '#options' => $options,
196
    '#default_value' => $values,
197
  );
198

    
199
  $form['security_review_adv']['check_settings'] = array(
200
    '#type' => 'fieldset',
201
    '#title' => t('Check-specific settings'),
202
    '#collapsible' => TRUE,
203
    '#collapsed' => TRUE,
204
    '#tree' => TRUE,
205
  );
206

    
207
  $form['security_review_adv']['check_settings']['security_review_base_url_method'] = array(
208
    '#type' => 'radios',
209
    '#title' => t('Base URL check method'),
210
    '#description' => t('Detecting the $base_url in settings.php can be done via PHP tokenization (recommend) or including the file. Note that if you have custom functionality in your settings.php it will be executed if the file is included. Including the file can result in a more accurate $base_url check if you wrap the setting in conditional statements.'),
211
    '#options' => array(
212
      'token' => t('Tokenize settings.php (recommended)'),
213
      'include' => t('Include settings.php'),
214
    ),
215
    '#default_value' => variable_get('security_review_base_url_method', 'token'),
216
  );
217
  // Add a submit handler to set the skipped checks.
218
  $form['#submit'][] = '_security_review_settings_submit';
219

    
220
  return system_settings_form($form);
221
}
222

    
223
function _security_review_settings_submit($form, &$form_state) {
224
  global $user;
225
  $log = $form_state['values']['security_review_log'];
226
  // Set checked.
227
  module_load_include('inc', 'security_review');
228
  $checklist = security_review_get_checklist();
229

    
230
  $stored = array();
231
  $results = db_query("SELECT namespace, reviewcheck, result, lastrun, skip, skiptime, skipuid FROM {security_review}");
232
  while($record = $results->fetchAssoc()) {
233
    $stored[$record['namespace']][$record['reviewcheck']] = $record;
234
  }
235

    
236
  foreach ($checklist as $module => $checks) {
237
    foreach ($checks as $check_name => $check) {
238
      $record = new stdClass();
239
      $update = array();
240
      // Toggle the skip.
241
      if (isset($stored[$module][$check_name]) && $stored[$module][$check_name]['skip'] == 1 && $form_state['values']['security_review_skip'][$check_name] === 0) {
242
        // We were skipping, so stop skipping and clear skip identifiers.
243
        $record->namespace = $module;
244
        $record->reviewcheck = $check_name;
245
        $record->skip = FALSE;
246
        $record->skiptime = 0;
247
        $record->skipuid = NULL;
248
        $result = drupal_write_record('security_review', $record, array('namespace', 'reviewcheck'));
249
        if ($log) {
250
          $variables = array('!name' => $check['title']);
251
          _security_review_log($module, $check_name, '!name check no longer skipped', $variables, WATCHDOG_INFO);
252
        }
253
      }
254
      elseif ($form_state['values']['security_review_skip'][$check_name] !== 0) {
255
        // Start skipping and record who made the decision and when.
256
        if (isset($stored[$module][$check_name])) {
257
          $update = array('namespace', 'reviewcheck');
258
        }
259
        $record->namespace = $module;
260
        $record->reviewcheck = $check_name;
261
        $record->skip = TRUE;
262
        $record->skiptime = REQUEST_TIME;
263
        $record->skipuid = $user->uid;
264
        $result = drupal_write_record('security_review', $record, $update);
265
        if ($log) {
266
          $variables = array('!name' => $check['title']);
267
          _security_review_log($module, $check_name, '!name check skipped', $variables, WATCHDOG_INFO);
268
        }
269
      }
270
    }
271
  }
272
  // Unset security_review_skip to keep it from being written to a variable.
273
  unset($form_state['values']['security_review_skip']);
274

    
275
  // Set check-specific settings.
276
  foreach ($form_state['values']['check_settings'] as $variable_name => $value) {
277
    variable_set($variable_name, $value);
278
  }
279
}
280

    
281
/**
282
 * Menu callback and Javascript callback for check skip toggling.
283
 */
284
function security_review_toggle_check($type = 'ajax', $check_name) {
285
  global $user;
286
  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $check_name)) {
287
    return drupal_access_denied();
288
  }
289
  $result = FALSE;
290
  // To be sure, compare the user-provided check with available checks.
291
  module_load_include('inc', 'security_review');
292
  $checklist = security_review_get_checklist();
293
  foreach ($checklist as $module => $checks) {
294
    if (in_array($check_name, array_keys($checks))) {
295
      $query = db_select('security_review', 'sr')
296
        ->fields('sr', array('namespace', 'reviewcheck', 'result', 'lastrun', 'skip', 'skiptime', 'skipuid'))
297
        ->condition('namespace', $module, '=')
298
        ->condition('reviewcheck', $check_name, '=');
299
      $record = $query->execute()->fetchObject();
300
      // Toggle the skip.
301
      if ($record->skip) {
302
        // We were skipping, so stop skipping and clear skip identifiers.
303
        $record->skip = FALSE;
304
        $record->skiptime = 0;
305
        $record->skipuid = NULL;
306
        $message = '!name check no longer skipped';
307
      }
308
      else {
309
        // Start skipping and record who made the decision and when.
310
        $record->skip = TRUE;
311
        $record->skiptime = REQUEST_TIME;
312
        $record->skipuid = $user->uid;
313
        $message = '!name check skipped';
314
      }
315
      $result = drupal_write_record('security_review', $record, array('namespace', 'reviewcheck'));
316
      // To log, or not to log?
317
      $log = variable_get('security_review_log', TRUE);
318
      if ($log) {
319
        $variables = array('!name' => $checks[$check_name]['title']);
320
        _security_review_log($module, $check_name, $message, $variables, WATCHDOG_INFO);
321
      }
322
      break;
323
    }
324
  }
325
  if ($type == 'ajax') {
326
    drupal_json_output($record);
327
    return;
328
  }
329
  else {
330
    // We weren't invoked via JS so set a message and return to the review page.
331
    drupal_set_message(t($message, array('!name' => $checks[$check_name]['title'])));
332
    drupal_goto('admin/reports/security-review');
333
  }
334
}
335

    
336
/**
337
 * Helper function creates message for reporting check skip information.
338
 */
339
function _security_review_check_skipped($last_check) {
340
  $account = array_pop(user_load_multiple(array($last_check['skipuid'])));
341
  $time = format_date($last_check['skiptime'], 'medium');
342
  $message = t('Check marked for skipping on !time by !user', array('!time' => $time, '!user' => theme('username', array('account' => $account))));
343
  return $message;
344
}
345

    
346
/**
347
 * Page callback provides general help and check specific help documentation.
348
 */
349
function security_review_check_help($module = NULL, $check_name = NULL) {
350
  // Include checks and help files.
351
  module_load_include('inc', 'security_review');
352
  $checklist = security_review_get_checklist();
353
  module_load_include('inc', 'security_review', 'security_review.help');
354
  $output = '';
355
  $skipped_message = NULL;
356
  if (!is_null($module) && !is_null($check_name)) {
357
    $check = $checklist[$module][$check_name];
358
    if (isset($check['help'])) {
359
      $output = $check['help'];
360
    }
361
    elseif (isset($check['callback'])) {
362
      if (isset($check['file'])) {
363
        $check_module = $module;
364
        // Handle Security Review defining checks for other modules.
365
        if (isset($check['module'])) {
366
          $check_module = $check['module'];
367
        }
368
        module_load_include('inc', $check_module, $check['file']);
369
      }
370
      $function = $check['callback'] . '_help';
371
      if (function_exists($function)) {
372
        $last_check = security_review_get_last_check($module, $check_name);
373
        // Set skipped timestamp and user message.
374
        if ($last_check['skip'] == '1') {
375
          $skipped_message = _security_review_check_skipped($last_check);
376
        }
377
        // Add findings to check if it's failed.
378
        elseif ($last_check['result'] == '0') {
379
          $callback = $check['callback'];
380
          $check_return = $callback();
381
          $last_check = array_merge($last_check, $check_return);
382
        }
383
        $element = $function($last_check, $skipped_message);
384
        $output = theme('security_review_check_help', array('element' => $element));
385
      }
386
    }
387
  }
388
  else {
389
    $output = _security_review_help();
390
    // List all checks as links to specific help.
391
    $output .= '<h3>' . t('Check-specfic help') . '</h3>';
392
    $output .= '<p>' . t("Details and help on the security review checks. Checks are not always perfectly correct in their procedure and result. Refer to drupal.org handbook documentation if you are unsure how to make the recommended alterations to your configuration or consult the module's README.txt for support.") . '</p>';
393
    foreach ($checklist as $module => $checks) {
394
      foreach ($checks as $reviewcheck => $check) {
395
        $items[] = l($check['title'], 'admin/reports/security-review/help/' . $module . '/' . $reviewcheck);
396
      }
397
    }
398
    if ($items) {
399
      $output .= theme('item_list', array('items' => $items));
400
    }
401
  }
402
  if (empty($output)) {
403
    return drupal_not_found();
404
  }
405
  return array('#markup' => $output);
406
}
407

    
408
function theme_security_review_reviewed($variables) {
409
  // @todo
410
  //drupal_add_js(drupal_get_path('module', 'security_review') . '/security_review.js', array('scope' => 'footer'));
411
  $output = '<h3>' . $variables['header'] . '</h3>';
412
  $output .= '<p>' . $variables['description'] . '</p>';
413
  $output .= '<table class="system-status-report">';
414
  if (!empty($variables['items'])) {
415
    foreach ($variables['items'] as $item) {
416
      $output .= '<tr class="' . $item['class'] . '">';
417
      $output .= '<td class="status-icon"><div title="' . $item['title'] . '"><span class="element-invisible">' . $item['title'] . '</span></div></td>';
418
      $output .= '<td>' . $item['message'] . '</td>';
419
      $output .= '<td>' . $item['help_link'] . '</td>';
420
      $output .= '<td>' . $item['toggle_link'] . '</td>';
421
      $output .= '</tr>';
422
    }
423
  }
424
  $output .= '</table>';
425
  return $output;
426
}
427

    
428
/**
429
 * Theme function for help on a security check.
430
 *
431
 * Calling function should filter and sanitize.
432
 */
433
function theme_security_review_check_help($variables) {
434
  $element = $variables['element'];
435
  $output = '<h3>' . $element['title'] . '</h3>';
436
  foreach ($element['descriptions'] as $description) {
437
    $output .= '<p>' . $description . '</p>';
438
  }
439
  if (!empty($element['findings'])) {
440
    foreach ($element['findings']['descriptions'] as $description) {
441
      $output .= '<p>' . $description . '</p>';
442
    }
443
    if (!empty($element['findings']['items'])) {
444
      $items = $element['findings']['items'];
445
      $output .= "<ul>\n";
446
      // Loop through items outputting the best value HTML, safe, or raw if thats all there is.
447
      foreach ($items as $item) {
448
        if (is_array($item)) {
449
          if (isset($item['html'])) {
450
            $data = $item['html'];
451
          }
452
          elseif (isset($item['safe'])) {
453
            $data = $item['safe'];
454
          }
455
          else {
456
            $data = $item['raw'];
457
          }
458
        }
459
        else {
460
          $data = $item;
461
        }
462
        $output .= "<li>" . $data . "</li>\n";
463
      }
464
      $output .= "</ul>\n";
465
    }
466
    if (!empty($element['findings']['pager'])) {
467
      $output .= $element['findings']['pager'];
468
    }
469
  }
470
  return $output;
471
}