Projet

Général

Profil

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

root / htmltest / sites / all / modules / cas / cas.module @ a5572547

1 85ad3d82 Assos Assos
<?php
2
3
/**
4
 * @file
5
 * Enables users to authenticate via a Central Authentication Service (CAS)
6
 * Cas will currently work if the auto registration is turned on and will
7
 * create user accounts automatically.
8
 */
9
10
define('CAS_NO_LINK', 0);
11
define('CAS_ADD_LINK', 1);
12
define('CAS_MAKE_DEFAULT', 2);
13
define('CAS_LOGIN_INVITE_DEFAULT', 'Log in using CAS');
14
define('CAS_LOGIN_DRUPAL_INVITE_DEFAULT', 'Cancel CAS login');
15
define('CAS_LOGIN_REDIR_MESSAGE', 'You will be redirected to the secure CAS login page.');
16
define('CAS_EXCLUDE', 'services/*');
17
18
/**
19
 * Implements hook_init().
20
 *
21
 * Traps a page load to see if authentication is required.
22
 */
23
function cas_init() {
24
  global $user;
25
26
  if (module_exists('cas_test') && arg(0) == 'cas_test') {
27
    // We are destined for a page handled by the cas_test module, so do not
28
    // do any processing here. Necessary for CAS gateway tests.
29
    return;
30
  }
31
32
  // Process a single-sign out request.
33
  _cas_single_sign_out_check();
34
35
  // If a user is not logged in, consider using CAS authentication.
36
  if (!$user->uid) {
37
    $force_authentication = _cas_force_login();
38
    $check_authentication = _cas_allow_check_for_login();
39
    if ($force_authentication || $check_authentication) {
40
      cas_login_check($force_authentication);
41
    }
42
  }
43
}
44
45
/**
46
 * Checks to see if the user needs to be logged in.
47
 *
48
 * @param $force_authentication
49
 *   If TRUE, require that the user be authenticated with the CAS server
50
 *   before proceeding. Otherwise, check with the CAS server to see if the
51
 *   user is already logged in.
52
 */
53
function cas_login_check($force_authentication = TRUE) {
54
  global $user;
55
  if ($user->uid) {
56
    //Don't Login  because we already are
57
    return;
58
  }
59
60
  if (!cas_phpcas_load()) {
61
    // No need to print a message, as the user will already see the failed
62
    // include_once calls.
63
    return;
64
  }
65
66
  // Start a drupal session
67
  drupal_session_start();
68
  _cas_single_sign_out_save_ticket();  // We use this later for CAS 3 logoutRequests
69
70
  // Initialize phpCAS.
71
  cas_phpcas_init();
72
73
  // We're going to try phpCAS auth test
74
  if ($force_authentication) {
75
    phpCAS::forceAuthentication();
76
  }
77
  else {
78
    $logged_in = phpCAS::checkAuthentication();
79
    // Set the login tested cookie
80
    setcookie('cas_login_checked', 'true');
81
82
    // We're done cause we're not logged in.
83
    if (!$logged_in) {
84
      return;
85
    }
86
  }
87
88
  // Build the cas_user object and allow modules to alter it.
89
  $cas_user = array(
90
    'name' => phpCAS::getUser(),
91
    'login' => TRUE,
92
    'register' => variable_get('cas_user_register', TRUE),
93
    'attributes' => cas_phpcas_attributes(),
94
  );
95
  drupal_alter('cas_user', $cas_user);
96
97
  // Bail out if a module denied login access for this user or unset the user
98
  // name.
99
  if (empty($cas_user['login']) || empty($cas_user['name'])) {
100
    // Only set a warning if we forced login.
101
    if ($force_authentication) {
102
      drupal_set_message(t('The user account %name is not available on this site.', array('%name' => $cas_name)), 'error');
103
    }
104
    return;
105
  }
106
107
  // Proceed with the login process, using the altered CAS username.
108
  $cas_name = $cas_user['name'];
109
110
  // blocked user check
111
  $blocked = FALSE;
112
  if (_cas_external_user_is_blocked($cas_name)) {
113
      $blocked = 'The username %cas_name has been blocked.';
114
  }
115
  // @todo The D7 equivalent here must have been renamed.
116
  // elseif (drupal_is_denied('user', $cas_name)) {
117
  //   // denied by access controls
118
  //   return 'The name %cas_name is a reserved username.';
119
  // }
120
121
  if ($blocked) {
122
    // Only display error messages only if the user intended to log in.
123
    if ($force_authentication) {
124
      watchdog('cas', $blocked, array('%cas_name' => $cas_name), WATCHDOG_WARNING);
125
      drupal_set_message(t($blocked, array('%cas_name' => $cas_name)), 'error');
126
    }
127
    return;
128
  }
129
130
  $account = cas_user_load_by_name($cas_name);
131
132
  // Automatic user registration.
133
  if (!$account && $cas_user['register']) {
134
    // No account could be found and auto registration is enabled, so attempt
135
    // to register a new user.
136
    $account = cas_user_register($cas_name);
137
    if (!$account) {
138
      // The account could not be created, set a message.
139
      if ($force_authentication) {
140
        drupal_set_message(t('A new account could not be created for %cas_name. The username is already in use on this site.', array('%cas_name' => $cas_name)), 'error');
141
      }
142
      return;
143
    }
144
  }
145
146
  // final check to make sure we have a good user
147
  if ($account && $account->uid > 0) {
148
    // Save the altered CAS name for future use.
149
    $_SESSION['cas_name'] = $cas_name;
150
151
    $cas_first_login = !$account->login;
152
153
    // Save single sign out information
154
    if (!empty($_SESSION['cas_ticket'])) {
155
      _cas_single_sign_out_save_token($account);
156
    }
157
158
    // Populate $edit with some basic properties.
159
    $edit['cas_user'] = $cas_user;
160
    $edit['roles'] = $account->roles + cas_roles();
161
    if (module_exists('persistent_login') && !empty($_SESSION['cas_remember'])) {
162
      $edit['persistent_login'] = 1;
163
    }
164
    // Allow other modules to make their own custom changes.
165
    cas_user_module_invoke('presave', $edit, $account);
166
167
    // Save the user account and log the user in.
168
    $user = user_save($account, $edit);
169
    user_login_finalize($edit);
170
171
    drupal_set_message(t(variable_get('cas_login_message', 'Logged in via CAS as %cas_username.'), array('%cas_username' => $user->name)));
172
    if (!empty($edit['persistent_login'])) {
173
      drupal_set_message(t('You will remain logged in on this computer even after you close your browser.'));
174
    }
175
176
    cas_login_page($cas_first_login);
177
  }
178
  else {
179
    $user = drupal_anonymous_user();
180
    // Only display error messages only if the user intended to log in.
181
    if ($force_authentication) {
182
      drupal_set_message(t('No account found for %cas_name.', array('%cas_name' => $cas_name)));
183
    }
184
  }
185
}
186
187
/**
188
 * Loads the phpCAS library.
189
 *
190
 * @param $path
191
 *   Attempt to load phpCAS using this path. If omitted, phpCAS will be loaded
192
 *   using Libraries API or the configured value.
193
 *
194
 * @return
195
 *   The phpCAS version if the phpCAS was successfully loaded, FALSE otherwise.
196
 */
197
function cas_phpcas_load($path = NULL) {
198
  if (!isset($path)) {
199
    if (module_exists('libraries')) {
200
      $path = libraries_get_path('CAS');
201
    }
202
    else {
203
      $path = variable_get('cas_library_dir', 'CAS');
204
    }
205
  }
206
207
  // Build the name of the file to load.
208
  if ($path != '') {
209
    $path = rtrim($path, '/') . '/';
210
  }
211
  $filename = $path . 'CAS.php';
212
213
  include_once($filename);
214
215
  if (!defined('PHPCAS_VERSION') || !class_exists('phpCAS')) {
216
    // The file could not be loaded successfully.
217
    return FALSE;
218
  }
219
  return PHPCAS_VERSION;
220
}
221
222
/**
223
 * Initialize phpCAS.
224
 *
225
 * Will load phpCAS if necessary.
226
 */
227
function cas_phpcas_init() {
228
  if (!defined('PHPCAS_VERSION') || !class_exists('phpCAS')) {
229
    cas_phpcas_load();
230
  }
231
232
  $initialized = &drupal_static(__FUNCTION__, FALSE);
233
  if ($initialized) {
234
    // phpCAS cannot be initialized twice. If you need to force this function
235
    // to run again, call drupal_static_reset('cas_phpcas_init') first.
236
    return;
237
  }
238
  $initialized = TRUE;
239
240
  // Variable set
241
  $server_version    = (string)variable_get('cas_version', '2.0');
242
  $server_cas_server = (string)variable_get('cas_server', 'sso-cas.univ-rennes1.fr');
243
  $server_port       = (int)variable_get('cas_port', '443');
244
  $server_uri        = (string)variable_get('cas_uri', '');
245
  $cas_cert          = (string)variable_get('cas_cert', '');
246
  if ( ($debugFile = variable_get("cas_debugfile", "")) != "" ) {
247
    phpCAS::setDebug($debugFile);
248
  }
249
  $start_session = (boolean)FALSE;
250
  if ( variable_get("cas_proxy", 0) ) {
251
    phpCAS::proxy($server_version, $server_cas_server, $server_port, $server_uri, $start_session);
252
    $casPGTStoragePath = variable_get("cas_pgtpath", "");
253
    if ( $casPGTStoragePath != "" ) {
254
      $casPGTFormat = variable_get("cas_pgtformat", "plain");
255
      phpCAS::setPGTStorageFile($casPGTFormat, $casPGTStoragePath);
256
    }
257
  }
258
  else {
259
    phpCAS::client($server_version, $server_cas_server, $server_port, $server_uri, $start_session);
260
  }
261
  // force CAS authentication
262
  if ($cas_cert = variable_get('cas_cert', '')) {
263
    phpCAS::setCasServerCACert($cas_cert);
264
  }
265
  else {
266
    phpCAS::setNoCasServerValidation();
267
  }
268
269
  $service = isset($_GET['q']) ? $_GET['q'] : 'cas';
270
  phpCAS::setFixedServiceURL(url($service, array('query' => cas_login_destination(), 'absolute' => TRUE)));
271
272
  // Allow other modules to call phpCAS routines. We do not call
273
  // drupal_alter() since there are no parameters to pass.
274
  module_invoke_all('cas_phpcas_alter');
275
}
276
277
278
/**
279
 * Implements hook_permission().
280
 */
281
function cas_permission() {
282
  return array(
283
    'administer cas' => array(
284
      'title' => t('Administer CAS'),
285
      'description' => t('Configure CAS server, default CAS user roles, login/logout redirection, and other settings.'),
286
      'restrict access' => TRUE,
287
    )
288
  );
289
}
290
291
/**
292
 * Implements hook_help().
293
 */
294
function cas_help($section) {
295
  switch ($section) {
296
    case 'admin/help#cas':
297
      return t("Allows users to authenticate via a Central Authentication Service.");
298
  }
299
}
300
301
/**
302
 * Implements hook_menu().
303
 */
304
function cas_menu() {
305
  global $user;
306
  $items = array();
307
  //cas_login_check();
308
  $items['admin/config/people/cas'] = array(
309
    'title' => 'CAS settings',
310
    'description' => 'Configure central authentication services',
311
    'page callback' => 'drupal_get_form',
312
    'page arguments' => array('cas_admin_settings'),
313
    'access arguments' => array('administer cas'),
314
    'type' => MENU_NORMAL_ITEM,
315
    'file' => 'cas.admin.inc',
316
  );
317
  $items['admin/config/people/cas/settings'] = array(
318
    'title' => 'CAS',
319
    'type' => MENU_DEFAULT_LOCAL_TASK,
320
    'weight' => -10,
321
  );
322
  $items['admin/people/cas/create'] = array(
323
    'title' => 'Add CAS user',
324
    'page callback' => 'drupal_get_form',
325
    'page arguments' => array('cas_add_user_form'),
326
    'access arguments' => array('administer users'),
327
    'type' => MENU_LOCAL_ACTION,
328
    'file' => 'cas.user.inc',
329
    'tab_parent' => 'admin/people',
330
    'weight' => 1,
331
  );
332
  $items['user/%user/cas'] = array(
333
    'title' => 'CAS',
334
    'page callback' => 'cas_user_identities',
335
    'page arguments' => array(1),
336
    'access arguments' => array('administer users'),
337
    'type' => MENU_LOCAL_TASK,
338
    'file' => 'cas.pages.inc',
339
    'tab_parent' => 'user/%/edit',
340
    'weight' => 1,
341
  );
342
  $items['user/%user/cas/delete'] = array(
343
    'title' => 'Delete CAS username',
344
    'page callback' => 'drupal_get_form',
345
    'page arguments' => array('cas_user_delete_form', 1),
346
    'access arguments' => array('administer users'),
347
    'file' => 'cas.pages.inc',
348
  );
349
  $items['cas'] = array(
350
    'path' => 'cas',
351
    'title' => 'CAS Login',
352
    'page callback' => 'cas_login_page',
353
    'access callback' => 'user_is_anonymous',
354
    'type' => MENU_SUGGESTED_ITEM,
355
  );
356
  $items['caslogout'] = array(
357
    'title' => 'CAS Logout',
358
    'page callback' => 'cas_logout',
359
    'access callback' => 'cas_user_is_logged_in',
360
    'type' => MENU_SUGGESTED_ITEM,
361
  );
362
  return $items;
363
}
364
365
function cas_user_is_logged_in() {
366
  return user_is_logged_in() || !empty($_SESSION['phpCAS']['user']);
367
}
368
369
/**
370
 * Implements hook_menu_site_status_alter().
371
 */
372
function cas_menu_site_status_alter(&$menu_site_status, $path) {
373
  if (user_is_logged_in() && $path == 'cas') {
374
    // If user is logged in, redirect to '<front>' instead of giving 403.
375
    drupal_goto('');
376
  }
377
}
378
379
/**
380
 * Implements hook_menu_link_alter().
381
 *
382
 * Flag this link as needing alter at display time.
383
 * @see cas_translated_menu_link_alter().
384
 **/
385
function cas_menu_link_alter(&$item) {
386
  if ($item['link_path'] == 'cas' || $item['link_path'] == 'caslogout') {
387
    $item['options']['alter'] = TRUE;
388
  }
389
}
390
391
/**
392
 * Implements hook_translated_menu_item_alter().
393
 *
394
 * Append dynamic query 'destination' to several menu items.
395
 **/
396
function cas_translated_menu_link_alter(&$item) {
397
  if ($item['href'] == 'cas') {
398
    $item['localized_options']['query'] = drupal_get_destination();
399
  }
400
  elseif ($item['href'] == 'caslogout' && !variable_get('cas_logout_destination', '')) {
401
    $item['localized_options']['query'] = drupal_get_destination();
402
  }
403
}
404
405
/**
406
 * Helper function to rewrite the destination to avoid redirecting to login page after login.
407
 *
408
 * Instead of the login page, we redirect to the front page.
409
 */
410
function cas_login_destination() {
411
  $destination = user_login_destination();
412
  if ($destination['destination'] == 'cas') {
413
    $destination['destination'] = '';
414
  }
415
  return $destination;
416
}
417
418
/**
419
 * Implements hook_user_operations().
420
 */
421
function cas_user_operations($form = array(), $form_state = array()) {
422
  $operations['cas_create'] = array(
423
    'label' => t('Create CAS username'),
424
    'callback' => 'cas_user_operations_create_username',
425
  );
426
  $operations['cas_remove'] = array(
427
    'label' => t('Remove CAS usernames'),
428
    'callback' => 'cas_user_operations_remove_usernames',
429
  );
430
  return $operations;
431
}
432
433
/**
434
 * Callback function to assign a CAS username to the account.
435
 *
436
 * @param $uids
437
 *   An array of user ids. For each account, a CAS username is created with
438
 *   the same name as the Drupal username.
439
 *
440
 * @see cas_user_operations().
441
 */
442
function cas_user_operations_create_username($uids) {
443
  $accounts = user_load_multiple($uids);
444
  foreach ($accounts as $account) {
445
    $count = db_select('cas_user', 'c')
446
      ->condition('cas_name', $account->name)
447
      ->condition('uid', $account->uid, '<>')
448
      ->countQuery()->execute()->fetchfield();
449
    if ($count) {
450
      drupal_set_message(t('CAS username %username already in use.', array('%username' => $account->name)), 'error');
451
      continue;
452
    }
453
    db_merge('cas_user')
454
      ->key(array('cas_name' => $account->name))
455
      ->fields(array('uid' => $account->uid))
456
      ->execute();
457
  }
458
}
459
460
/**
461
 * Callback function to remove CAS usernames from the account.
462
 *
463
 * @param $uids
464
 *   An array of user ids. For each account, all CAS usernames are removed.
465
 *
466
 * @see cas_user_operations().
467
 */
468
function cas_user_operations_remove_usernames($uids) {
469
  db_delete('cas_user')
470
    ->condition('uid', $uids, 'IN')
471
    ->execute();
472
}
473
474
/**
475
 * Implements hook_admin_paths().
476
 */
477
function cas_admin_paths() {
478
  $paths = array(
479
    'user/*/cas' => TRUE,
480
    'user/*/cas/delete/*' => TRUE,
481
  );
482
  return $paths;
483
}
484
485
/**
486
 * Implements hook_user_load().
487
 *
488
 * Adds an associative array 'cas_names' to each user. The array keys are
489
 * unique authentication mapping ids, with CAS usernames as the values.
490
 */
491
function cas_user_load($users) {
492
  foreach (array_keys($users) as $uid) {
493
    $users[$uid]->cas_names = array();
494
  }
495
  $result = db_query('SELECT aid, uid, cas_name FROM {cas_user} WHERE uid IN (:uids)', array(':uids' => array_keys($users)));
496
  foreach ($result as $record) {
497
    $users[$record->uid]->cas_names[$record->aid] = $record->cas_name;
498
  }
499
  foreach (array_keys($users) as $uid) {
500
    $users[$uid]->cas_name = reset($users[$uid]->cas_names);
501
  }
502
}
503
504
/**
505
 * Implements hook_user_insert().
506
 *
507
 * When a user is created, record their CAS username if provided.
508
 */
509
function cas_user_insert(&$edit, $account, $category) {
510
  if (!empty($edit['cas_name'])) {
511
    db_insert('cas_user')
512
      ->fields(array(
513
        'cas_name' => $edit['cas_name'],
514
        'uid' => $account->uid,
515
      ))
516
      ->execute();
517
  }
518
  // Update $account to reflect changes.
519
  $users = array($account->uid => $account);
520
  cas_user_load($users);
521
}
522
523
/**
524
 * Implements hook_user_update().
525
 *
526
 * When a user is updated, change their CAS username if provided.
527
 */
528
function cas_user_update(&$edit, $account, $category) {
529
  if (!array_key_exists('cas_name', $edit)) {
530
    // If the cas_name key is not provided, there is nothing to do.
531
    return;
532
  }
533
  $cas_name = $edit['cas_name'];
534
535
  // See if the user currently has any CAS names.
536
  reset($account->cas_names);
537
  if ($aid = key($account->cas_names)) {
538
    // The user already has CAS username(s) set.
539
    if (empty($cas_name)) {
540
      // Remove a CAS username.
541
      db_delete('cas_user')
542
        ->condition('uid', $account->uid)
543
        ->condition('aid', $aid)
544
        ->execute();
545
    }
546
    else {
547
      // Change a CAS username.
548
      if ($cas_name != $account->cas_names[$aid]) {
549
        db_update('cas_user')
550
          ->fields(array('cas_name' => $cas_name))
551
          ->condition('uid', $account->uid)
552
          ->condition('aid', $aid)
553
          ->execute();
554
      }
555
    }
556
  }
557
  else {
558
    // No current CAS usernames.
559
    if (!empty($cas_name)) {
560
      // Add a CAS username.
561
      db_insert('cas_user')
562
        ->fields(array(
563
          'uid' => $account->uid,
564
          'cas_name' => $cas_name,
565
        ))
566
        ->execute();
567
    }
568
  }
569
  // Update $account to reflect changes.
570
  $users = array($account->uid => $account);
571
  cas_user_load($users);
572
}
573
574
/**
575
 * Implement hook_user_delete().
576
 *
577
 * When a CAS user is deleted, we need to clean up the entry in {cas_user}.
578
 */
579
function cas_user_delete($account) {
580
  db_delete('cas_user')
581
    ->condition('uid', $account->uid)
582
    ->execute();
583
}
584
585
/**
586
 * Fetch a user object by CAS name.
587
 *
588
 * @param $cas_name
589
 *   The name of the CAS user.
590
 * @param $alter
591
 *   If TRUE, run the CAS username through hook_cas_user_alter() before
592
 *   loading the account.
593
 *
594
 * @return
595
 *   A fully-loaded $user object upon successful user load or FALSE if user
596
 *   cannot be loaded.
597
 */
598
function cas_user_load_by_name($cas_name, $alter = FALSE) {
599
  if ($alter) {
600
    $cas_user = array(
601
      'name' => $cas_name,
602
      'login' => TRUE,
603
      'register' => FALSE,
604
    );
605
    drupal_alter('cas_user', $cas_user);
606
    $cas_name = $cas_user['name'];
607
  }
608
609
  $uid = db_query("SELECT uid FROM {cas_user} WHERE cas_name = :cas_name", array(':cas_name' => $cas_name))->fetchField();
610
  if ($uid) {
611
    return user_load($uid);
612
  }
613
  return FALSE;
614
}
615
616
/**
617
 * Redirects to appropriate page based on user settings.
618
 *
619
 * @param $cas_first_login
620
 *   TRUE if the user was just registered and they should be redirected to the
621
 *   configured 'Initial login landing page'.
622
 */
623
function cas_login_page($cas_first_login = FALSE) {
624
  global $user;
625
  $destination = '';
626
  // If it is the user's first CAS login and initial login redirection is enabled, go to the set page
627
  if ($cas_first_login && variable_get('cas_first_login_destination', '')) {
628
    $destination = variable_get('cas_first_login_destination', '');
629
    unset($_GET['destination']);
630
  }
631
632
  // Respect the query string, if transmitted.
633
  drupal_goto($destination);
634
}
635
636
/**
637
 * Logs a user out of Drupal and then out of CAS.
638
 *
639
 * This function does not return, but instead immediately redirects the user
640
 * to the CAS server to complete the CAS logout process.
641
 *
642
 * Other modules intending to call this from their implementation of
643
 * hook_user_logout() will need to pass $invoke_hook = FALSE to avoid an
644
 * infinite recursion. WARNING: since this function does not return, any
645
 * later implementations of hook_user_logout() will not run. You may wish to
646
 * adjust the hook execution order using hook_module_implements_alter().
647
 *
648
 * @param $invoke_hook
649
 *   If TRUE, invoke hook_user_logout() and save a watchdog mesage indicating
650
 *   that the user has logged out.
651
 */
652
function cas_logout($invoke_hook = TRUE) {
653
  global $user;
654
655
  // Build the logout URL.
656
  cas_phpcas_init();
657
  $logout_url = phpCAS::getServerLogoutURL();
658
  $options = array();
659
660
  if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
661
    // Add destination override so that a destination can be specified on the
662
    // logout link, e.g., caslogout?desination=http://foo.bar.com/foobar. We do
663
    // not allow absolute URLs to be passed via $_GET, as this can be an attack
664
    // vector.
665
    $destination = $_GET['destination'];
666
  }
667
  else {
668
    $destination = variable_get('cas_logout_destination', '');
669
  }
670
671
  //Make it an absolute url.  This will also convert <front> to the front page.
672
  if ($destination) {
673
    $destination_url = url($destination, array('absolute' => TRUE));
674
    $options['query'] = array(
675
      'destination' => $destination_url,
676
      'service' => $destination_url,
677
      'url' => $destination_url,
678
    );
679
  }
680
681
  // Mimic user_logout().
682
  if ($invoke_hook) {
683
    watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
684
    module_invoke_all('user_logout', $user);
685
  }
686
  session_destroy();
687
688
  // Force redirection in drupal_goto().
689
  unset($_GET['destination']);
690
  drupal_goto($logout_url, $options);
691
}
692
693
/**
694
 * Implements hook_block_info().
695
 */
696
function cas_block_info() {
697
  $blocks['login']['info'] = t('CAS login');
698
  // Not worth caching.
699
  $blocks['login']['cache'] = DRUPAL_NO_CACHE;
700
701
  return $blocks;
702
}
703
704
/**
705
 * Implements hook_block_view().
706
 */
707
function cas_block_view($delta = '') {
708
  global $user;
709
710
  $block = array();
711
712
  switch ($delta) {
713
    case 'login':
714
      // For usability's sake, avoid showing two login forms on one page.
715
      if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
716
        $block['subject'] = t('User login');
717
        $block['content'] = drupal_get_form('cas_login_block');
718
      }
719
      return $block;
720
  }
721
}
722
723
/**
724
 * Login form for the CAS login block.
725
 */
726
function cas_login_block($form) {
727
  $form['#action'] = url('cas', array('query' => drupal_get_destination()));
728
  $form['#id'] = 'cas-login-form';
729
730
  $form['cas_login_redirection_message'] = array(
731
    '#type' => 'item',
732
    '#markup' => t(variable_get('cas_login_redir_message', CAS_LOGIN_REDIR_MESSAGE)),
733
    '#weight' => -1,
734
  );
735
  $form['actions'] = array('#type' => 'actions');
736
  $form['actions']['submit'] = array(
737
    '#type' => 'submit',
738
    '#value' => t(variable_get('cas_login_invite', CAS_LOGIN_INVITE_DEFAULT)),
739
  );
740
741
  return $form;
742
}
743
744
/**
745
 * Determine if we should automatically check if the user is authenticated.
746
 *
747
 * This implements part of the CAS gateway feature.
748
 * @see phpCAS::checkAuthentication().
749
 *
750
 * @return
751
 *   TRUE if we should query the CAS server to see if the user is already
752
 *   authenticated, FALSE otherwise.
753
 */
754
function _cas_allow_check_for_login() {
755
  if (!variable_get('cas_check_first', 0)) {
756
    // The user has disabled the feature.
757
    return FALSE;
758
  }
759
760
  // Check to see if we already have.
761
  if (!empty($_COOKIE['cas_login_checked'])) {
762
    return FALSE;
763
  }
764
765
  // Check to see if we've got a search bot.
766
  $crawlers = array(
767
    'Google',
768
    'msnbot',
769
    'Rambler',
770
    'Yahoo',
771
    'AbachoBOT',
772
    'accoona',
773
    'AcoiRobot',
774
    'ASPSeek',
775
    'CrocCrawler',
776
    'Dumbot',
777
    'FAST-WebCrawler',
778
    'GeonaBot',
779
    'Gigabot',
780
    'Lycos',
781
    'MSRBOT',
782
    'Scooter',
783
    'AltaVista',
784
    'IDBot',
785
    'eStyle',
786
    'Scrubby',
787
    'gsa-crawler',
788
    );
789
  // Return on the first find.
790
  foreach ($crawlers as $c) {
791
    if (stripos($_SERVER['HTTP_USER_AGENT'], $c) !== FALSE) {
792
      return FALSE;
793
    }
794
  }
795
796
  // Do not force login for XMLRPC, Cron, or Drush.
797
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'xmlrpc.php')) {
798
    return FALSE;
799
  }
800
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'cron.php')) {
801
    return FALSE;
802
  }
803
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'drush')) {
804
    return FALSE;
805
  }
806
  if (!empty($_SERVER['argv'][0]) && stristr($_SERVER['argv'][0], 'drush')) {
807
    return FALSE;
808
  }
809
810
  // Test against exclude pages.
811
  if ($pages = variable_get('cas_exclude', CAS_EXCLUDE)) {
812
    $path = drupal_get_path_alias($_GET['q']);
813
    if (drupal_match_path($path, $pages)) {
814
      return FALSE;
815
    }
816
  }
817
818
  return TRUE;
819
}
820
821
/**
822
 * Determine if we should require the user be authenticated.
823
 *
824
 * @return
825
 *   TRUE if we should require the user be authenticated, FALSE otherwise.
826
 */
827
function _cas_force_login() {
828
  // The 'cas' page is a shortcut to force authentication.
829
  if (arg(0) == 'cas') {
830
    return TRUE;
831
  }
832
833
  // Do not force login for XMLRPC, Cron, or Drush.
834
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'xmlrpc.php')) {
835
    return FALSE;
836
  }
837
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'cron.php')) {
838
    return FALSE;
839
  }
840
  if (function_exists('drush_verify_cli') && drush_verify_cli()) {
841
    return FALSE;
842
  }
843
844
  // Excluded page do not need login.
845
  if ($pages = variable_get('cas_exclude', CAS_EXCLUDE)) {
846
    $path = drupal_get_path_alias($_GET['q']);
847
    if (drupal_match_path($path, $pages)) {
848
      return FALSE;
849
    }
850
  }
851
852
  // Set the default behavior.
853
  $force_login = variable_get('cas_access', 0);
854
855
  // If we match the speficied paths, reverse the behavior.
856
  if ($pages = variable_get('cas_pages', '')) {
857
    $path = drupal_get_path_alias($_GET['q']);
858
    if (drupal_match_path($path, $pages)) {
859
      $force_login = !$force_login;
860
    }
861
  }
862
863
  return $force_login;
864
}
865
/**
866
 * Implements hook_form_alter().
867
 *
868
 * Overrides specific from settings based on user policy.
869
 */
870
function cas_form_alter(&$form, &$form_state, $form_id) {
871
872
  //drupal_set_message($form_id.'<pre>'.print_r($form,1).'</pre>');
873
  switch ($form_id) {
874
    case 'user_login':
875
    case 'user_login_block':
876
      if (variable_get('cas_login_form', CAS_NO_LINK) != CAS_NO_LINK) {
877
        $form['#attached']['css'][] = drupal_get_path('module', 'cas') . '/cas.css';
878
        $form['#attached']['js'][] = drupal_get_path('module', 'cas') . '/cas.js';
879
880
        if (!empty($form_state['input']['cas_identifier'])) {
881
          $form['name']['#required'] = FALSE;
882
          $form['pass']['#required'] = FALSE;
883
          unset($form['#validate']);
884
          $form['#submit'] = array('cas_login_submit');
885
        }
886
887
        $items = array();
888
        $items[] = array(
889
          'data' => l(t(variable_get('cas_login_invite', CAS_LOGIN_INVITE_DEFAULT)), '#'),
890
          'class' => array('cas-link'),
891
        );
892
893
        $items[] = array(
894
          'data' => l(t(variable_get('cas_login_drupal_invite', CAS_LOGIN_DRUPAL_INVITE_DEFAULT)), '#'),
895
          'class' => array('uncas-link'),
896
        );
897
898
        $form['cas_links'] = array(
899
          '#theme' => 'item_list',
900
          '#items' => $items,
901
          '#attributes' => array('class' => array('cas-links')),
902
          '#weight' => 1,
903
        );
904
905
        $form['links']['#weight'] = 2;
906
907
        $form['cas_login_redirection_message'] = array(
908
          '#type' => 'item',
909
          '#markup' => t(variable_get('cas_login_redir_message', CAS_LOGIN_REDIR_MESSAGE)),
910
          '#weight' => -1,
911
        );
912
913
        $form['cas_identifier'] = array(
914
          '#type' => 'checkbox',
915
          '#title' => t(variable_get('cas_login_invite', CAS_LOGIN_INVITE_DEFAULT)),
916
          '#default_value' => variable_get('cas_login_form', CAS_NO_LINK) == CAS_MAKE_DEFAULT,
917
          '#weight' => -1,
918
          '#description' => t(variable_get('cas_login_redir_message', CAS_LOGIN_REDIR_MESSAGE)),
919
        );
920
        $form['cas.return_to'] = array('#type' => 'hidden', '#value' => user_login_destination());
921
      }
922
      break;
923
924
    case 'user_profile_form':
925
      $account = $form['#user'];
926
      if (user_access('administer users')) {
927
        // The user is an administrator, so add fields to allow changing the
928
        // CAS username(s) associated with the account.
929
        $cas_names = $account->cas_names;
930
        $aids = array_keys($cas_names);
931
932
        $element = array(
933
          '#type' => 'textfield',
934
          '#title' => t('CAS username'),
935
          '#default_value' => array_shift($cas_names),
936
          '#cas_user_aid' => array_shift($aids),
937
          '#description' => t('<a href="@url">Create, edit or delete</a> additional CAS usernames associated with this account.', array('@url' => url('user/' . $account->uid . '/cas'))),
938
          '#element_validate' => array('_cas_name_element_validate'),
939
          '#weight' => -9,
940
        );
941
942
        // See if any additional CAS usernames exist.
943
        if (!empty($cas_names)) {
944
          $element['#description'] .= ' <br />' . t('Other CAS usernames: %cas_names.', array('%cas_names' => implode(', ', $cas_names)));
945
        }
946
        $form['account']['cas_name'] = $element;
947
      }
948
      elseif (cas_is_external_user($account)) {
949
        // The user is not an administrator, so selectively remove the e-mail
950
        // and password fields.
951
        if (variable_get('cas_hide_email', 0)) {
952
          $form['account']['mail']['#access'] = FALSE;
953
        }
954
        if (variable_get('cas_hide_password', 0)) {
955
          $form['account']['pass']['#access'] = FALSE;
956
957
          // Also remove requirement to validate your current password before
958
          // changing your e-mail address.
959
          $form['account']['current_pass']['#access'] = FALSE;
960
          $form['account']['current_pass_required_values']['#access'] = FALSE;
961
          $form['#validate'] = array_diff($form['#validate'], array('user_validate_current_pass'));
962
        }
963
      }
964
      break;
965
966
    case 'user_pass':
967
      if (!user_access('administer users') && variable_get('cas_changePasswordURL', '') != '') {
968
        drupal_goto(variable_get('cas_changePasswordURL', ''));
969
      }
970
      break;
971
972
    case 'user_register_form':
973
      if (user_access('administer users')) {
974
        $form['account']['cas_name'] = array(
975
          '#type' => 'textfield',
976
          '#title' => t('CAS username'),
977
          '#default_value' => '',
978
          '#description' => t('If necessary, additional CAS usernames can be added after the account is created.'),
979
          '#element_validate' => array('_cas_name_element_validate'),
980
          '#weight' => -9,
981
        );
982
      }
983
      elseif (variable_get('cas_registerURL', '') != '') {
984
        drupal_goto(variable_get('cas_registerURL', ''));
985
      }
986
      break;
987
988
    case 'user_admin_account':
989
      // Insert the CAS username into the second column.
990
      _cas_array_insert($form['accounts']['#header'], 1, array(
991
        'cas' => array(
992
          'data' => 'CAS usernames',
993
        ),
994
      ));
995
      foreach ($form['accounts']['#options'] as $uid => &$row) {
996
        $cas_usernames = db_query('SELECT cas_name FROM {cas_user} WHERE uid = :uid', array(':uid' => $uid))->fetchCol();
997
        $row['cas'] = theme('item_list', array('items' => $cas_usernames));
998
      }
999
      break;
1000
  }
1001
}
1002
1003
/**
1004
 * Form element 'cas_name' validator.
1005
 *
1006
 * If the element is disaplying an existing {cas_user} entry, set
1007
 * #cas_user_aid to the corresponing authmap id to avoid spurious
1008
 * validation errors.
1009
 */
1010
function _cas_name_element_validate($element, &$form_state) {
1011
  if (empty($element['#value'])) {
1012
    // Nothing to validate if the name is empty.
1013
    return;
1014
  }
1015
1016
  $query = db_select('cas_user')
1017
    ->fields('cas_user', array('uid'))
1018
    ->condition('cas_name', $element['#value']);
1019
1020
  // If set, we ignore entries with a specified authmap id. This is used on
1021
  // the user/%user/edit page to not throw validation errors when we do not
1022
  // change the CAS username.
1023
  if (isset($element['#cas_user_aid'])) {
1024
    $query->condition('aid', $element['#cas_user_aid'], '<>');
1025
  }
1026
1027
  $uid = $query->execute()->fetchField();
1028
1029
  if ($uid !== FALSE) {
1030
    // Another user is using this CAS username.
1031
    form_set_error('cas_name', t('The CAS username is <a href="@edit-user-url">already in use</a> on this site.', array('@edit-user-url' => url('user/' . $uid . '/edit'))));
1032
  }
1033
}
1034
1035
/**
1036
 * Login form _validate hook
1037
 */
1038
function cas_login_submit(&$form, &$form_state) {
1039
  if (!empty($form_state['values']['persistent_login'])) {
1040
    $_SESSION['cas_remember'] = 1;
1041
  }
1042
  // Force redirection.
1043
  unset($_GET['destination']);
1044
  drupal_goto('cas', array('query' => $form_state['values']['cas.return_to']));
1045
}
1046
1047
function _cas_single_sign_out_check() {
1048
  if (isset($_POST["logoutRequest"])) {
1049
    $cas_logout_request_xml_string = utf8_encode($_POST["logoutRequest"]);     // it's important!
1050
    $cas_logout_request_xml = new SimpleXMLElement($cas_logout_request_xml_string);
1051
    if (is_object($cas_logout_request_xml)) {
1052
      $namespaces = $cas_logout_request_xml->getNameSpaces();
1053
      $xsearch = 'SessionIndex';
1054
      if (isset($namespaces['samlp'])) {
1055
        $cas_logout_request_xml->registerXPathNamespace('samlp', $namespaces['samlp']);
1056
        $xsearch = 'samlp:SessionIndex';
1057
      }
1058
      $cas_session_indexes = $cas_logout_request_xml->xpath($xsearch);
1059
      if ($cas_session_indexes) {
1060
        $cas_session_index = (string)$cas_session_indexes[0];
1061
        // Log them out now.
1062
        // first lets find out who we want to log off
1063
1064
        $result = db_query_range("SELECT cld.uid FROM {cas_login_data} cld WHERE cld.cas_session_id = :ticket", 0 , 1, array(':ticket' => $cas_session_index));
1065
        foreach ($result as $record) {
1066
            $uid = $record->uid;
1067
            $acct = user_load($uid);
1068
            watchdog('user', 'Session closed for %name.', array('%name' => $acct->name));
1069
            // remove all entry for user id in cas_login_data
1070
            db_delete('cas_login_data')
1071
              ->condition('uid', $uid)
1072
              ->execute();
1073
1074
            // remove their session
1075
            db_delete('sessions')
1076
              ->condition('uid', $uid)
1077
              ->execute();
1078
        }
1079
      }
1080
    }
1081
    // This request is done, so just exit.
1082
    exit();
1083
  }
1084
}
1085
1086
/**
1087
 * Return the current CAS username.
1088
 */
1089
function cas_current_user() {
1090
  return isset($_SESSION['cas_name']) ? $_SESSION['cas_name'] : FALSE;
1091
}
1092
1093
/**
1094
 * Determine whether the specified user is an "external" CAS user.
1095
 * When settings are set to use drupal as the user repository, then this
1096
 * function will always return true.
1097
 *
1098
 * @param $account
1099
 *   The user object for the user to query. If omitted, the current user is
1100
 *   used.
1101
 *
1102
 * @return
1103
 *   TRUE if the user is logged in via CAS.
1104
 */
1105
function cas_is_external_user($account = NULL) {
1106
  if (!isset($account)) {
1107
    $account = $GLOBALS['user'];
1108
  }
1109
  return in_array(cas_current_user(), $account->cas_names);
1110
}
1111
1112
1113
function _cas_single_sign_out_save_token($user) {
1114
  // Ok lets save the CAS service ticket to DB so
1115
  // we can handle CAS logoutRequests when they come
1116
  if ($user->uid && $user->uid > 0 && !empty($_SESSION['cas_ticket'])) {
1117
    db_insert('cas_login_data')
1118
      ->fields(array(
1119
        'cas_session_id' => $_SESSION['cas_ticket'],
1120
        'uid' => $user->uid,
1121
        ))
1122
      ->execute();
1123
    unset($_SESSION['cas_ticket']);
1124
  }
1125
}
1126
1127
/**
1128
 * Make sure that we persist ticket because of redirects performed by CAS.
1129
 *
1130
 */
1131
function _cas_single_sign_out_save_ticket() {
1132
  if (isset($_GET['ticket'])) {
1133
    $_SESSION['cas_ticket'] = $_GET['ticket'];
1134
  }
1135
}
1136
1137
/**
1138
 * Determine whether a CAS user is blocked.
1139
 *
1140
 * @param $cas_name
1141
 *   The CAS username.
1142
 *
1143
 * @return
1144
 *   Boolean TRUE if the user is blocked, FALSE if the user is active.
1145
 */
1146
function _cas_external_user_is_blocked($cas_name) {
1147
  return db_query("SELECT name FROM {users} u JOIN {cas_user} c ON u.uid = c.uid WHERE u.status = 0 AND c.cas_name = :cas_name", array(':cas_name' => $cas_name))->fetchField();
1148
}
1149
1150
/**
1151
 * Invokes hook_cas_user_TYPE() in every module.
1152
 *
1153
 * We cannot use module_invoke() because the arguments need to be passed by
1154
 * reference.
1155
 */
1156
function cas_user_module_invoke($type, &$edit, $account) {
1157
  foreach (module_implements('cas_user_' . $type) as $module) {
1158
    $function = $module . '_cas_user_' . $type;
1159
    $function($edit, $account);
1160
  }
1161
}
1162
1163
/**
1164
 * Roles which should be granted to all CAS users.
1165
 *
1166
 * @return
1167
 *   An associative array with the role id as the key and the role name as value.
1168
 */
1169
function cas_roles() {
1170
  $cas_roles = &drupal_static(__FUNCTION__);
1171
  if (!isset($cas_roles)) {
1172
    $cas_roles = array_intersect_key(user_roles(), array_filter(variable_get('cas_auto_assigned_role', array(DRUPAL_AUTHENTICATED_RID => TRUE))));
1173
  }
1174
  return $cas_roles;
1175
}
1176
1177
/**
1178
 * Register a CAS user with some default values.
1179
 *
1180
 * @param $cas_name
1181
 *   The name of the CAS user.
1182
 * @param $options
1183
 *   An associative array of options, with the following elements:
1184
 *    - 'edit': An array of fields and values for the new user. If omitted,
1185
 *      reasonable defaults are used.
1186
 *    - 'invoke_cas_user_presave': Defaults to FALSE. Whether or not to invoke
1187
 *      hook_cas_user_presave() on the newly created account.
1188
 *
1189
 * @return
1190
 *   The user object of the created user, or FALSE if the user cannot be
1191
 *   created.
1192
 */
1193
function cas_user_register($cas_name, $options = array()) {
1194
  // Add some reasonable defaults if they have not yet been provided.
1195
  $edit = isset($options['edit']) ? $options['edit'] : array();
1196
  $edit += array(
1197
    'name' => $cas_name,
1198
    'pass' => user_password(),
1199
    'init' => $cas_name,
1200
    'mail' => variable_get('cas_domain', '') ? $cas_name . '@' . variable_get('cas_domain', '') : '',
1201
    'status' => 1,
1202
    'roles' => array(),
1203
  );
1204
  $edit['roles'] += cas_roles();
1205
  $edit['cas_name'] = $cas_name;
1206
1207
  // See if the user name is already taken.
1208
  if ((bool) db_select('users')->fields('users', array('name'))->condition('name', db_like($edit['name']), 'LIKE')->range(0, 1)->execute()->fetchField()) {
1209
    return FALSE;
1210
  }
1211
1212
  // Create the user account.
1213
  $account = user_save(drupal_anonymous_user(), $edit);
1214
  watchdog("user", 'new user: %n (CAS)', array('%n' => $account->name), WATCHDOG_NOTICE, l(t("edit user"), "admin/user/edit/$account->uid"));
1215
1216
  if (!empty($options['invoke_cas_user_presave'])) {
1217
    // Populate $edit with some basic properties.
1218
    $edit = array(
1219
      'cas_user' => array(
1220
        'name' => $cas_name,
1221
      ),
1222
    );
1223
1224
    // Allow other modules to make their own custom changes.
1225
    cas_user_module_invoke('presave', $edit, $account);
1226
1227
    // Clean up extra variables before saving.
1228
    unset($edit['cas_user']);
1229
1230
    $account = user_save($account, $edit);
1231
  }
1232
1233
  // Reload to ensure that we have a fully populated user object.
1234
  return user_load($account->uid);
1235
}
1236
1237
/**
1238
 * Get the CAS attributes of the current CAS user.
1239
 *
1240
 * Ensures that phpCAS is properly initialized before getting the attributes.
1241
 * @see phpCAS::getAttributes().
1242
 *
1243
 * @param $cas_name
1244
 *   If provided, ensure that the currently logged in CAS user matches this
1245
 *   CAS username.
1246
 *
1247
 * @return
1248
 *   An associative array of CAS attributes.
1249
 */
1250
function cas_phpcas_attributes($cas_name = NULL) {
1251
  if (isset($cas_name) && $cas_name != cas_current_user()) {
1252
    // Attributes cannot be extracted for other users, since they are
1253
    // stored in the session variable.
1254
    return array();
1255
  }
1256
1257
  cas_phpcas_init();
1258
  if (phpCAS::isAuthenticated()) {
1259
    if (method_exists('phpCAS', 'getAttributes')) {
1260
      return phpCAS::getAttributes();
1261
    }
1262
  }
1263
1264
  return array();
1265
}
1266
1267
1268
/**
1269
 * Insert an array into the specified position of another array.
1270
 *
1271
 * Preserves keys in associative arrays.
1272
 * @see http://www.php.net/manual/en/function.array-splice.php#56794
1273
 */
1274
function _cas_array_insert(&$array, $position, $insert_array) {
1275
  $first_array = array_splice($array, 0, $position);
1276
  $array = array_merge($first_array, $insert_array, $array);
1277
}
1278
1279
/**
1280
 * Implements hook_views_api().
1281
 */
1282
function cas_views_api() {
1283
  return array(
1284
    'api' => 3,
1285
    'path' => drupal_get_path('module', 'cas') . '/includes/views',
1286
  );
1287
}