Projet

Général

Profil

Paste
Télécharger (18 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / modules / security_review / security_review.pages.inc @ c12e7e6a

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
    $message = $check['result'] ? $checklist[$check['namespace']][$check['reviewcheck']]['success'] : $checklist[$check['namespace']][$check['reviewcheck']]['failure'];
45
    $title = $check['result'] ? t('OK') : t('Error');
46
    $class = $check['skip'] ? 'info' : ($check['result'] ? 'ok' : 'error');
47
    $toggle = $check['skip'] ? t('Enable') : t('Skip');
48
    $token = drupal_get_token($check['reviewcheck']);
49
    $link_options = array(
50
      'query' => array('token' => $token),
51
      //'attributes' => array('class' => 'use-ajax'),
52
    );
53
    $items[] = array(
54
      'title' => $title,
55
      'value' => $check['result'],
56
      'class' => $class,
57
      'message' => $message,
58
      'help_link' => l(t('Details'), 'admin/reports/security-review/help/' . $check['namespace'] . '/' . $check['reviewcheck']),
59
      'toggle_link' => l($toggle, 'admin/reports/security-review/toggle/nojs/' . $check['reviewcheck'], $link_options),
60
    );
61
  }
62
  $output = theme('security_review_reviewed', array('items' => $items, 'header' => $header, 'description' => $desc));
63
  // @todo #markup?
64
  return array('#markup' => $output);
65
}
66

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

    
80
  return $form;
81
}
82

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

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

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

    
134
  $form['security_review_untrusted_roles'] = array(
135
    '#type' => 'checkboxes',
136
    '#title' => t('Untrusted roles'),
137
    '#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'))),
138
    '#options' => $options,
139
    '#default_value' => variable_get('security_review_untrusted_roles', array_keys($defaults)),
140
  );
141

    
142
  $form['security_review_adv'] = array(
143
    '#type' => 'fieldset',
144
    '#title' => t('Advanced'),
145
    '#collapsible' => TRUE,
146
    '#collapsed' => FALSE,
147
  );
148
  $form['security_review_adv']['security_review_log'] = array(
149
    '#type' => 'checkbox',
150
    '#title' => t('Log checklist results and skips'),
151
    '#description' => t('The result of each check and skip can be logged to watchdog for tracking.'),
152
    '#default_value' => variable_get('security_review_log', TRUE),
153
  );
154
  $options = $values = array();
155

    
156
  $skipped = security_review_skipped_checks();
157
  foreach ($checklist as $module => $checks) {
158
    foreach ($checks as $check_name => $check) {
159
      // Determine if check is being skipped.
160
      if (!empty($skipped) && array_key_exists($check_name, $skipped[$module])) {
161
        $values[] = $check_name;
162
        $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'])));
163
      }
164
      else {
165
        $label = $check['title'];
166
      }
167
      $options[$check_name] = $label;
168
    }
169
  }
170
  $form['security_review_adv']['security_review_skip'] = array(
171
    '#type' => 'checkboxes',
172
    '#title' => t('Checks to skip'),
173
    '#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.'),
174
    '#options' => $options,
175
    '#default_value' => $values,
176
  );
177

    
178
  $form['security_review_adv']['check_settings'] = array(
179
    '#type' => 'fieldset',
180
    '#title' => t('Check-specific settings'),
181
    '#collapsible' => TRUE,
182
    '#collapsed' => TRUE,
183
    '#tree' => TRUE,
184
  );
185

    
186
  $form['security_review_adv']['check_settings']['security_review_base_url_method'] = array(
187
    '#type' => 'radios',
188
    '#title' => t('Base URL check method'),
189
    '#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.'),
190
    '#options' => array(
191
      'token' => t('Tokenize settings.php (recommended)'),
192
      'include' => t('Include settings.php'),
193
    ),
194
    '#default_value' => variable_get('security_review_base_url_method', 'token'),
195
  );
196
  // Add a submit handler to set the skipped checks.
197
  $form['#submit'][] = '_security_review_settings_submit';
198

    
199
  return system_settings_form($form);
200
}
201

    
202
function _security_review_settings_submit($form, &$form_state) {
203
  global $user;
204
  $log = $form_state['values']['security_review_log'];
205
  // Set checked.
206
  module_load_include('inc', 'security_review');
207
  $checklist = security_review_get_checklist();
208

    
209
  $stored = array();
210
  $results = db_query("SELECT namespace, reviewcheck, result, lastrun, skip, skiptime, skipuid FROM {security_review}");
211
  while($record = $results->fetchAssoc()) {
212
    $stored[$record['namespace']][$record['reviewcheck']] = $record;
213
  }
214

    
215
  foreach ($checklist as $module => $checks) {
216
    foreach ($checks as $check_name => $check) {
217
      $record = new stdClass();
218
      $update = array();
219
      // Toggle the skip.
220
      if (isset($stored[$module][$check_name]) && $stored[$module][$check_name]['skip'] == 1 && $form_state['values']['security_review_skip'][$check_name] === 0) {
221
        // We were skipping, so stop skipping and clear skip identifiers.
222
        $record->namespace = $module;
223
        $record->reviewcheck = $check_name;
224
        $record->skip = FALSE;
225
        $record->skiptime = 0;
226
        $record->skipuid = NULL;
227
        $result = drupal_write_record('security_review', $record, array('namespace', 'reviewcheck'));
228
        if ($log) {
229
          $variables = array('!name' => $check['title']);
230
          _security_review_log($module, $check_name, '!name check no longer skipped', $variables, WATCHDOG_INFO);
231
        }
232
      }
233
      elseif ($form_state['values']['security_review_skip'][$check_name] !== 0) {
234
        // Start skipping and record who made the decision and when.
235
        if (isset($stored[$module][$check_name])) {
236
          $update = array('namespace', 'reviewcheck');
237
        }
238
        $record->namespace = $module;
239
        $record->reviewcheck = $check_name;
240
        $record->skip = TRUE;
241
        $record->skiptime = REQUEST_TIME;
242
        $record->skipuid = $user->uid;
243
        $result = drupal_write_record('security_review', $record, $update);
244
        if ($log) {
245
          $variables = array('!name' => $check['title']);
246
          _security_review_log($module, $check_name, '!name check skipped', $variables, WATCHDOG_INFO);
247
        }
248
      }
249
    }
250
  }
251
  // Unset security_review_skip to keep it from being written to a variable.
252
  unset($form_state['values']['security_review_skip']);
253

    
254
  // Set check-specific settings.
255
  foreach ($form_state['values']['check_settings'] as $variable_name => $value) {
256
    variable_set($variable_name, $value);
257
  }
258
}
259

    
260
/**
261
 * Menu callback and Javascript callback for check skip toggling.
262
 */
263
function security_review_toggle_check($type = 'ajax', $check_name) {
264
  global $user;
265
  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $check_name)) {
266
    return drupal_access_denied();
267
  }
268
  $result = FALSE;
269
  // To be sure, compare the user-provided check with available checks.
270
  module_load_include('inc', 'security_review');
271
  $checklist = security_review_get_checklist();
272
  foreach ($checklist as $module => $checks) {
273
    if (in_array($check_name, array_keys($checks))) {
274
      $query = db_select('security_review', 'sr')
275
        ->fields('sr', array('namespace', 'reviewcheck', 'result', 'lastrun', 'skip', 'skiptime', 'skipuid'))
276
        ->condition('namespace', $module, '=')
277
        ->condition('reviewcheck', $check_name, '=');
278
      $record = $query->execute()->fetchObject();
279
      // Toggle the skip.
280
      if ($record->skip) {
281
        // We were skipping, so stop skipping and clear skip identifiers.
282
        $record->skip = FALSE;
283
        $record->skiptime = 0;
284
        $record->skipuid = NULL;
285
        $message = '!name check no longer skipped';
286
      }
287
      else {
288
        // Start skipping and record who made the decision and when.
289
        $record->skip = TRUE;
290
        $record->skiptime = REQUEST_TIME;
291
        $record->skipuid = $user->uid;
292
        $message = '!name check skipped';
293
      }
294
      $result = drupal_write_record('security_review', $record, array('namespace', 'reviewcheck'));
295
      // To log, or not to log?
296
      $log = variable_get('security_review_log', TRUE);
297
      if ($log) {
298
        $variables = array('!name' => $checks[$check_name]['title']);
299
        _security_review_log($module, $check_name, $message, $variables, WATCHDOG_INFO);
300
      }
301
      break;
302
    }
303
  }
304
  if ($type == 'ajax') {
305
    drupal_json_output($record);
306
    return;
307
  }
308
  else {
309
    // We weren't invoked via JS so set a message and return to the review page.
310
    drupal_set_message(t($message, array('!name' => $checks[$check_name]['title'])));
311
    drupal_goto('admin/reports/security-review');
312
  }
313
}
314

    
315
/**
316
 * Helper function creates message for reporting check skip information.
317
 */
318
function _security_review_check_skipped($last_check) {
319
  $account = array_pop(user_load_multiple(array($last_check['skipuid'])));
320
  $time = format_date($last_check['skiptime'], 'medium');
321
  $message = t('Check marked for skipping on !time by !user', array('!time' => $time, '!user' => theme('username', array('account' => $account))));
322
  return $message;
323
}
324

    
325
/**
326
 * Page callback provides general help and check specific help documentation.
327
 */
328
function security_review_check_help($module = NULL, $check_name = NULL) {
329
  // Include checks and help files.
330
  module_load_include('inc', 'security_review');
331
  $checklist = security_review_get_checklist();
332
  module_load_include('inc', 'security_review', 'security_review.help');
333
  $output = '';
334
  $skipped_message = NULL;
335
  if (!is_null($module) && !is_null($check_name)) {
336
    $check = $checklist[$module][$check_name];
337
    if (isset($check['help'])) {
338
      $output = $check['help'];
339
    }
340
    elseif (isset($check['callback'])) {
341
      if (isset($check['file'])) {
342
        // Handle Security Review defining checks for other modules.
343
        if (isset($check['module'])) {
344
          $module = $check['module'];
345
        }
346
        module_load_include('inc', $module, $check['file']);
347
      }
348
      $function = $check['callback'] . '_help';
349
      if (function_exists($function)) {
350
        $last_check = security_review_get_last_check($module, $check_name);
351
        // Set skipped timestamp and user message.
352
        if ($last_check['skip'] == '1') {
353
          $skipped_message = _security_review_check_skipped($last_check);
354
        }
355
        // Add findings to check if it's failed.
356
        elseif ($last_check['result'] == '0') {
357
          $callback = $check['callback'];
358
          $check_return = $callback();
359
          $last_check = array_merge($last_check, $check_return);
360
        }
361
        $element = $function($last_check, $skipped_message);
362
        $output = theme('security_review_check_help', array('element' => $element));
363
      }
364
    }
365
  }
366
  else {
367
    $output = _security_review_help();
368
    // List all checks as links to specific help.
369
    $output .= '<h3>' . t('Check-specfic help') . '</h3>';
370
    $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>';
371
    foreach ($checklist as $module => $checks) {
372
      foreach ($checks as $reviewcheck => $check) {
373
        $items[] = l($check['title'], 'admin/reports/security-review/help/' . $module . '/' . $reviewcheck);
374
      }
375
    }
376
    if ($items) {
377
      $output .= theme('item_list', array('items' => $items));
378
    }
379
  }
380
  if (empty($output)) {
381
    return drupal_not_found();
382
  }
383
  return array('#markup' => $output);
384
}
385

    
386
function theme_security_review_reviewed($variables) {
387
  // @todo
388
  //drupal_add_js(drupal_get_path('module', 'security_review') . '/security_review.js', array('scope' => 'footer'));
389
  $output = '<h3>' . $variables['header'] . '</h3>';
390
  $output .= '<p>' . $variables['description'] . '</p>';
391
  $output .= '<table class="system-status-report">';
392
  if (!empty($variables['items'])) {
393
    foreach ($variables['items'] as $item) {
394
      $output .= '<tr class="' . $item['class'] . '">';
395
      $output .= '<td class="status-icon"><div title="' . $item['title'] . '"><span class="element-invisible">' . $item['title'] . '</span></div></td>';
396
      $output .= '<td>' . $item['message'] . '</td>';
397
      $output .= '<td>' . $item['help_link'] . '</td>';
398
      $output .= '<td>' . $item['toggle_link'] . '</td>';
399
      $output .= '</tr>';
400
    }
401
  }
402
  $output .= '</table>';
403
  return $output;
404
}
405

    
406
/**
407
 * Theme function for help on a security check.
408
 *
409
 * Calling function should filter and sanitize.
410
 */
411
function theme_security_review_check_help($variables) {
412
  $element = $variables['element'];
413
  $output = '<h3>' . $element['title'] . '</h3>';
414
  foreach ($element['descriptions'] as $description) {
415
    $output .= '<p>' . $description . '</p>';
416
  }
417
  if (!empty($element['findings'])) {
418
    foreach ($element['findings']['descriptions'] as $description) {
419
      $output .= '<p>' . $description . '</p>';
420
    }
421
    if (!empty($element['findings']['items'])) {
422
      $items = $element['findings']['items'];
423
      $output .= "<ul>\n";
424
      // Loop through items outputting the best value HTML, safe, or raw if thats all there is.
425
      foreach ($items as $item) {
426
        if (is_array($item)) {
427
          if (isset($item['html'])) {
428
            $data = $item['html'];
429
          }
430
          elseif (isset($item['safe'])) {
431
            $data = $item['safe'];
432
          }
433
          else {
434
            $data = $item['raw'];
435
          }
436
        }
437
        else {
438
          $data = $item;
439
        }
440
        $output .= "<li>" . $data . "</li>\n";
441
      }
442
      $output .= "</ul>\n";
443
    }
444
    if (!empty($element['findings']['pager'])) {
445
      $output .= $element['findings']['pager'];
446
    }
447
  }
448
  return $output;
449
}