/[drupal]/contributions/modules/netforum_authentication/netforum_authentication.module
ViewVC logotype

Contents of /contributions/modules/netforum_authentication/netforum_authentication.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.14 - (show annotations) (download) (as text)
Sun Oct 18 18:45:46 2009 UTC (5 weeks, 5 days ago) by jamesmichaelhill
Branch: MAIN
CVS Tags: DRUPAL-5--0-11, HEAD
Changes since 1.13: +26 -22 lines
File MIME type: text/x-php
Update member role settings, better AES login cookie encryption support
1 <?php
2 // $Id: netforum_authentication.module,v 1.13 2009/07/11 13:42:47 jamesmichaelhill Exp $
3
4 //TODO: There are two places still calling GetQuery w/o regard to OD or Enterprise. One for the forgot password section,
5 // and one for the load section. The load section needs to be rewritten to use the data from the login (if possible) or
6 // to use a separate function to get user data. Maybe not load data on login?
7
8 // This is used for the shared SSO, since the user redirects after login we have to pause a beat before
9 // including the magic link in the page
10 if ($_SESSION['netforum_auth_link']) {
11 drupal_add_link($_SESSION['netforum_auth_link']);
12 unset($_SESSION['netforum_auth_link']);
13 }
14
15
16 /**
17 * Implementation of hook_menu()
18 */
19 function netforum_authentication_menu($may_cache) {
20 $items = array();
21 global $user;
22
23 if ($may_cache) {
24 $items[] = array(
25 'path' => 'admin/user/netforum-authentication',
26 'title' => t('netFORUM user authentication'),
27 'description' => t('Set the authentication settings for netFORUM customers'),
28 'callback' => 'drupal_get_form',
29 'callback arguments' => array('netforum_auth_admin_settings'),
30 'access' => user_access('administer site configuration'),
31 );
32
33 $items[] = array(
34 'path' => 'admin/user/netforum-roles',
35 'title' => t('netFORUM user roles'),
36 'description' => t('Choose what site roles are given to netFORUM users'),
37 'callback' => 'drupal_get_form',
38 'callback arguments' => array('netforum_auth_role_settings'),
39 'access' => user_access('administer site configuration') || user_access('administer access control'),
40 );
41
42 $items[] = array('path' => 'admin/settings/netforum/clearcache',
43 'title' => t('Clear menu cache'),
44 'callback' => 'netforum_auth_cache_clear',
45 'access' => user_access('administer site configuration'),
46 'type' => MENU_CALLBACK,
47 );
48
49 $items[] = array('path' => 'user/netforum_sso_check',
50 'title' => t('netFORM Authentication Shared SSO Check'),
51 'callback' => 'netforum_auth_shared_sso_check',
52 'access' => true,
53 'type' => MENU_CALLBACK,
54 );
55
56 $items[] = array('path' => 'user/netforum_sso_share',
57 'title' => t('netFORM Authentication Shared SSO'),
58 'callback' => 'netforum_auth_shared_sso_login',
59 'access' => true,
60 'type' => MENU_CALLBACK,
61 );
62
63 $items[] = array('path' => 'nf_od_sso_transfer',
64 'title' => t('Transfer to netFORUM OD w/ SSO'),
65 'callback' => 'netforum_auth_od_sso_transfer',
66 'access' => true,
67 'type' => MENU_CALLBACK,
68 );
69
70
71 //here we will overwrite some special user URLs if we want to alter the behavior, if this isn't working check the load
72 // order of the modules to make sure netFORUM loads AFTER the user module. Load order is determined by weight in the
73 //system table and it should be set automatically on installation
74 if (strtolower(variable_get('netforum_auth_forgotten_password', '<drupal>')) != '<drupal>' || variable_get('netforum_auth_forgot_password_add', 0) == 1) {
75 $items[] = array('path' => 'user/password', 'title' => t('Request new password'),
76 'callback' => 'netforum_auth_forgotten_password_redirect', 'callback arguments' => array('user_pass'), 'access' => !$user->uid, 'type' => MENU_LOCAL_TASK);
77 }
78
79 if (strtolower(variable_get('netforum_auth_user_register', '<drupal>')) != '<drupal>') {
80 $items[] = array('path' => 'user/register', 'title' => t('Create new account'),
81 'callback' => 'netforum_auth_user_register_redirect', 'callback arguments' => '', 'access' => !$user->uid && variable_get('user_register', 1), 'type' => MENU_LOCAL_TASK);
82 }
83
84 }
85 else{
86 if ( (arg(0) == 'user' && is_numeric(arg(1)) && arg(1) > 0) && $user !== FALSE) {
87 if (strtolower(variable_get('netforum_auth_user_editing', '<drupal>')) != '<drupal>') {
88 $items[] = array('path' => 'user/'. arg(1) .'/edit', 'title' => t('Edit'),
89 'callback' => 'netforum_auth_user_editing_redirect', 'callback arguments' => '',
90 'access' => user_access('administer users') || $user->uid == arg(1), 'type' => MENU_LOCAL_TASK);
91 }
92 }
93 }
94 return $items;
95 }
96
97 /**
98 * direct the user to the correct edit info page
99 *
100 * called by the user/ID/edit url if drupal is set to use an external site for editing user info
101 */
102 function netforum_auth_user_editing_redirect() {
103 global $user;
104 if (netforum_is_empty_guid($user->cst_key) === false && strtolower(variable_get('netforum_auth_user_editing', '<drupal>')) != '<drupal>') {
105 $loc = variable_get('netforum_auth_user_editing', '');
106 if (netforum_is_team() && stristr($loc, "netforumondemand.com")) {
107 netforum_auth_od_sso_transfer($loc);
108 }
109 else {
110 drupal_goto($loc);
111 }
112 }
113 else {
114 return drupal_get_form('user_edit');
115 }
116 }
117
118 /**
119 * direct the user to the correct forgot password page
120 *
121 * called by the user/password url if we're overwriting the default drupal behavior
122 */
123 function netforum_auth_forgotten_password_redirect() {
124 if (strtolower(variable_get('netforum_auth_forgotten_password', '<drupal>')) != '<drupal>') {
125 drupal_goto(variable_get('netforum_auth_forgotten_password', ''));
126 }
127 elseif (variable_get('netforum_auth_forgot_password_add', 0) == 1) {
128 return drupal_get_form('netforum_auth_user_pass');
129 }
130 else {
131 return drupal_get_form('user_pass');
132 }
133 }
134
135 /**
136 * direct the user to the correct registration page
137 *
138 * called by the user/register url if we're overwriting the default drupal behavior
139 */
140 function netforum_auth_user_register_redirect() {
141 if (strtolower(variable_get('netforum_auth_user_register', '<drupal>')) != '<drupal>') {
142 drupal_goto(variable_get('netforum_auth_user_register', ''));
143 }
144 else {
145 return drupal_get_form('user_register');
146 }
147 }
148
149
150 /**
151 * The user password form
152 *
153 * This is called by the netforum_auth_forgotten_password_redirect function if they want to allow users who have
154 * not logged in to request a password. Really we overwrite it because we want our own validation form.
155 * @ingroup forms
156 */
157 function netforum_auth_user_pass() {
158 $form = user_pass() ;
159 $form['#validate'] = array('netforum_auth_user_pass_validate' => array());
160 $form['#submit'] = array('user_pass_submit' => array());
161 return $form;
162 }
163
164 /**
165 * Add the user if they exist in netFORUM when validating the login form
166 *
167 * This is a modified version of the regular user_pass_validate function that
168 * does the additional step of checking to see if the user exists in netFORUM, and if they do
169 * then it creates a new drupal user and declares netforum_authentication to be the authentication
170 * module. That way they can reset their password if using drupal's password reset function
171 */
172 function netforum_auth_user_pass_validate($form_id, $form_values) {
173 $name = $form_values['name'];
174 $account = user_load(array('mail' => $name, 'status' => 1));
175 if (!$account) {
176 $account = user_load(array('name' => $name, 'status' => 1));
177 }
178 if (!$account) {
179 $eml_field = _netforum_auth_get_email_field('Customer');
180 $query = array('szObjectName' => 'Customer @TOP 1',
181 'szColumnList' => 'cst_type, cst_web_login, '. $eml_field,
182 'szWhereClause' => "(". $eml_field ." = '$name' OR cst_web_login = '$name')",
183 'szOrderBy' => '');
184 $response = netforum_xweb_request('GetQuery', $query);
185 if ($response && $response->attributes()->recordReturn == 1) {
186 $cst_obj = $response->CustomerObject;
187 $cst_name = (string)$cst_obj->cst_web_login;
188 $cst_mail = (string)$cst_obj->{$eml_field};
189 $cst_type = (string)$cst_obj->cst_type;
190 $cst_key = (string)$cst_obj->cst_key;
191
192 if (empty($cst_name)) {
193 $cst_name = $cst_mail;
194 }
195 //adapted from user_authenticate in the user module to add a new user
196 $userinfo = array('name' => $cst_name, 'mail' => $cst_mail, 'pass' => user_password(), 'cst_key' => $cst_key, 'cst_type' => $cst_type, 'init' => $name, 'status' => 1 );
197 $userinfo["authname_netforum_authentication"] = $name;
198 $account = user_save('', $userinfo);
199 watchdog('user', t('New external user: %user using module %module.', array('%user' => $name, '%module' => 'netforum_authentication')), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit'));
200 }
201 }
202 if ($account->uid) {
203 form_set_value(array('#parents' => array('account')), $account);
204 }
205 else {
206 form_set_error('name', t('Sorry, %name is not recognized as a user name or an email address.', array('%name' => $name)));
207 }
208 }
209
210
211 /**
212 * Define the admin settings form
213 * @ingroup forms
214 */
215 function netforum_auth_admin_settings() {
216 $form['settings'] = array(
217 '#title' => "User authentication settings",
218 '#type' => 'fieldset',
219 '#description' => t("Determine what parts of user authentication and information drupal is responsible for."),
220 );
221
222 $form['settings']['netforum_auth_user_register'] = array(
223 '#title' => t('New User Registration'),
224 '#type' => 'textfield',
225 '#description' => t('Enter the URL users should be redirected to when they click on create new account. This can be an external URL or a drupal node, such as node/newuserinfo. Enter &lt;drupal&gt; to use the defaul drupal new user screen'),
226 '#size' => 40,
227 '#required' => TRUE,
228 '#default_value' => variable_get('netforum_auth_user_register', '<drupal>'),
229 );
230
231 $form['settings']['netforum_auth_forgotten_password'] = array(
232 '#title' => t('Forgotten Passwords'),
233 '#type' => 'textfield',
234 '#description' => t('Enter the URL users should be redirected to to reset their password, or &lt;drupal&gt; to use built in functionality at user/password.'),
235 '#size' => 40,
236 '#required' => TRUE,
237 '#default_value' => variable_get('netforum_auth_forgotten_password', '<drupal>'),
238 );
239
240 $form['settings']['netforum_auth_forgot_password_add'] = array(
241 '#title' => t('Search netFORUM for users with forgotten passwords'),
242 '#type' => 'checkbox',
243 '#description' => t('If using drupal for forgotten passwords, should Drupal check both local users and netFORUM when looking for usernames? If this is not checked only users who have already logged in can reset their password with Drupal. '),
244 '#size' => 40,
245 '#default_value' => variable_get('netforum_auth_forgot_password_add', 0),
246 );
247
248 $form['settings']['netforum_auth_user_editing'] = array(
249 '#title' => t('Edit User info'),
250 '#type' => 'textfield',
251 '#description' => t('Enter the URL users should be redirected to to edit their information, or &lt;drupal&gt; to use built in functionality. Note that this includes setting passwords and email addresses by default. '),
252 '#size' => 40,
253 '#required' => TRUE,
254 '#default_value' => variable_get('netforum_auth_user_editing', '<drupal>'),
255 );
256
257 if (netforum_is_team()) {
258 $form['settings']['netforum_auth_user_text'] = array(
259 '#title' => t('Edit User notes'),
260 '#type' => 'textarea',
261 '#description' => t('If the edit user info URL above is set to &lt;drupal&gt;, this block of text will appear at the top of the user edit info form instead of the username and password. Use this space to provide notes and links to locations where users should edit their username and password if desired.'),
262 '#default_value' => variable_get('netforum_auth_user_text',''),
263 );
264
265 $form['settings']['netforum_auth_user_editing']['#description'] .= t('<b>Note:</b> netFORUM On Demand cannot update user information, no changes to user name or password will be allowed on the drupal form.');
266 $form['settings']['netforum_auth_forgot_password_add']['#description'] .= t('<b>Note:</b> netFORUM On Demand cannot update user information, no changes to user name or password will be allowed on the drupal form.');
267 }
268
269 $form['settings']['cache_clear'] = array(
270 '#title' => 'Menu troubleshooting',
271 '#type' => 'item',
272 '#description' => t('If the settings above are not working properly try '. l(t('clearing the menu cache'), 'admin/settings/netforum/clearcache')),
273 );
274
275 //Enterprise only
276 if (netforum_is_enterprise()) {
277 $form['sso'] = array(
278 '#title' => "Single Sign On settings",
279 '#type' => 'fieldset',
280 '#description' => t("Which eWeb sites should a user automatically be logged on to when logged in to drupal?"),
281 );
282
283 $form['sso']['netforum_auth_eweb_sso'] = array(
284 '#title' => t('eWeb Sites'),
285 '#type' => 'checkboxes',
286 '#description' => t('Select all to log users onto every eweb site in netFORUM or pick only the sites you want'),
287 '#options' => array_merge(array('ALL' => 'All eweb sites'), netforum_auth_eweb_sites()),
288 '#default_value' => variable_get('netforum_auth_eweb_sso', array()),
289 );
290
291 $form['sso']['netforum_auth_cookie_domain'] = array(
292 '#title' => t('SSO Cookie domain'),
293 '#type' => 'textfield',
294 '#description' => t('To use SSO both eWeb and your drupal installation must share the same top level domain (tld). For example, if you have drupal running www.example.com then the address for eWeb needs to be something that ends in example.com like store.example.com and this field should be set to .example.com'),
295 '#size' => 40,
296 '#default_value' => variable_get('netforum_auth_cookie_domain', $_SERVER['HTTP_HOST']),
297 );
298
299 $form['sso']['netforum_auth_sso_logout_url'] = array(
300 '#title' => t('SSO logout URL'),
301 '#type' => 'textfield',
302 '#description' => t('When a user logs out of drupal any netFORUM cookies set for the domain above will be deleted, but the user can also be directed to a url that will log them out of eWeb such as http://eweb.example.com/eWeb/Logout.aspx . If this is set to &lt;drupal&gt; then only the cookies will be removed, and the user will be logged out of eWeb when they close the browser.'),
303 '#validate' => array('netforum_valid_sso_logout' => array()),
304 '#size' => 40,
305 '#default_value' => variable_get('netforum_auth_sso_logout_url', '<drupal>'),
306 );
307
308 /*
309 NOTE: This was added in response to http://drupal.org/node/454578 - although current testing suggests the old style
310 of setting the login and pw to the modified md5 plaintext password still works with the current version of netforum
311 */
312 $form['sso']['pw_encryption'] = array(
313 '#title' => "Custom SSO Encryption Key",
314 '#type' => 'fieldset',
315 '#description' => t("For netFORUM 2.2 and above, these fields depend on the eWebUserPasswordCookieEncryptionKey setting in eweb/web.config. Leave blank to use the pre-2.2 style of SSO."),
316 );
317 $form['sso']['pw_encryption']['netforum_auth_custom_password_encryption_key'] = array(
318 '#title' => t('AES Key'),
319 '#type' => 'textfield',
320 '#description' => t('The encryption key should be the Base 64 encoded. It must be generated for you based on the value of the eWebUserPasswordCookieEncryptionKey. '),
321 '#size' => 40,
322 '#required' => FALSE,
323 '#default_value' => variable_get('netforum_auth_custom_password_encryption_key', ''),
324 );
325 $form['sso']['pw_encryption']['netforum_auth_custom_password_encryption_iv'] = array(
326 '#title' => t('AES Initialization Vector (IV)'),
327 '#type' => 'textfield',
328 '#description' => t('The IV should also be Base 64 encoded and shorter than the key.'),
329 '#size' => 30,
330 '#required' => FALSE,
331 '#default_value' => variable_get('netforum_auth_custom_password_encryption_iv', ''),
332 );
333
334
335 $form['sso']['netforum_auth_shared_sso'] = array(
336 '#title' => t('Allow other drupal sites to share this domain for SSO'),
337 '#type' => 'checkbox',
338 '#description' => t("Sometimes users might log into another website running Drupal with a different domain than eWeb, in that case SSO won't work. Checking this will allow other websites to use this site and domain for logging in to eWeb."),
339 '#default_value' => variable_get('netforum_auth_shared_sso', 0),
340 );
341
342 $form['sso']['netforum_auth_shared_sso_secret'] = array(
343 '#title' => t('Shared secret password'),
344 '#type' => 'textfield',
345 '#description' => t('Both sites sharing a domain for SSO must have the same shared secret password. This is used to help ensure that only authorized sites can log users on.'),
346 '#default_value' => variable_get('netforum_auth_shared_sso_secret', ''),
347 '#size' => 40,
348 );
349
350 $form['sso']['netforum_auth_shared_sso_url'] = array(
351 '#title' => t('Shared SSO url'),
352 '#type' => 'textfield',
353 '#description' => t('To use another website for eWeb SSO, enter the URL of the site here. The website must also have netFORUM Authentication enabled, a shared key, and allowing other sites must be checked. The cookie domain and eweb site settings listed above will be ignored, and the settings on the remote site will be used instead.'),
354 '#default_value' => variable_get('netforum_auth_shared_sso_url', ''),
355 '#size' => 40,
356 );
357 }
358
359 return system_settings_form($form);
360 }
361
362 function netforum_auth_admin_settings_validate($form_id, $form_values) {
363 if (($form_values['netforum_auth_shared_sso'] == 1 || $form_values['netforum_auth_shared_sso_url'] != '') && trim($form_values['netforum_auth_shared_sso_secret']) == '' ) {
364 form_set_error('netforum_auth_shared_sso_secret', t('The shared secret is required when using this domain for shared sso'));
365 }
366 else {
367 $secret_okay = true;
368 }
369
370 if (trim($form_values['netforum_auth_shared_sso_url']) != '' && substr($form_values['netforum_auth_shared_sso_url'], 0, 7) != 'http://' && substr($form_values['netforum_auth_shared_sso_url'], 0, 8) != 'https://') {
371 form_set_error('netforum_auth_shared_sso_url', t('Shared SSO url must start with http:// or https://'));
372 }
373 else {
374 $url_okay = true;
375 }
376
377 if ( $secret_okay === true && $url_okay === true && trim($form_values['netforum_auth_shared_sso_url']) != '' ) {
378 if (ini_get('allow_url_fopen')) {
379 $check_url = $form_values['netforum_auth_shared_sso_url'];
380 if (substr($check_url, -1) != '/') {
381 $check_url .= "/";
382 }
383 $token = md5("netFORUMAUTHINTERNAL". $form_values['netforum_auth_shared_sso_secret']);
384 $check_url .= "user/netforum_sso_check/". $token;
385 $remote_site_response = file_get_contents($check_url);
386 if (stristr($remote_site_response, "<checkresult>1</checkresult>") === FALSE) {
387 form_set_error('form', t("The shared SSO site either could not be reached or the shared secret didn't match."));
388 }
389 }
390 }
391
392 if (trim($form_values['netforum_auth_custom_password_encryption_key']) != '' || trim($form_values['netforum_auth_custom_password_encryption_iv']) != '') {
393 $key = trim($form_values['netforum_auth_custom_password_encryption_key']);
394 $iv = trim($form_values['netforum_auth_custom_password_encryption_iv']);
395
396 # TODO: The two else statements should check for appropriate key length, but mb_strlen is returning less than expected. Instead we
397 # run one test encryption to see if it works. If there are errors Drupal will catch and display them.
398 netforum_aes_encrypt("test", $key, $iv);
399
400
401 if ($key == '') {
402 form_set_error('netforum_auth_custom_password_encryption_key', t('SSO key cannot be blank if SSO IV is set'));
403 }
404 // else {
405 // $keylen = mb_strlen(base64_decode($key));
406 // if ($keylen != 32) {
407 // form_set_error('netforum_auth_custom_password_encryption_key', t('SSO key is the wrong size, 32 bytes expected but %size found after decoding.', array('%size' => $keylen)));
408 // }
409 // }
410
411 if ($iv == '') {
412 form_set_error('netforum_auth_custom_password_encryption_iv', t('SSO IV cannot be blank if SSO key is set'));
413 }
414 // else {
415 // $keylen = mb_strlen(base64_decode($iv));
416 // if ($keylen != 16) {
417 // form_set_error('netforum_auth_custom_password_encryption_iv', t('SSO IV is the wrong size, 16 bytes expected but %size found after decoding.', array('%size' => $keylen)));
418 // }
419 // }
420
421 }
422
423 if (strtolower($form_values['netforum_auth_user_register']) != '<drupal>' || strtolower($form_values['netforum_auth_forgotten_password']) != '<drupal>' || strtolower($form_values['netforum_auth_user_editing']) != '<drupal>') {
424 cache_clear_all('*', 'cache_menu', TRUE);
425 }
426 }
427
428 /**
429 * The form describing netforum user roles
430 *
431 * @ingroup forms
432 */
433 function netforum_auth_role_settings() {
434 $ignore_roles = netforum_auth_ignore_roles();
435
436 $form['desc'] = array(
437 '#type' => 'item',
438 '#description' => t('For each of the following roles on the drupal website, identify which individual or organizations should receive those roles'),
439 );
440
441 $form['#submit'] = array('netforum_auth_role_settings_submit' => array());
442
443 $categories = netforum_auth_categories();
444 $netforum_auth_roles = variable_get('netforum_auth_roles', array_keys(user_roles(1)));
445 // This iterates only on the roles we are NOT ignoring, by default this is authenticated and anonymous
446 foreach (array_diff_key(user_roles(), $ignore_roles) as $rid => $role) {
447 $form[$rid] = array(
448 '#title' => $role,
449 '#type' => 'fieldset',
450 '#description' => t("Which netFORUM users are assigned to the drupal site role %role ?", array('%role' => $role) ),
451 '#collapsible' => TRUE,
452 '#collapsed' => TRUE,
453 );
454 $form[$rid]["netforum_auth_roles_$rid"] = array(
455 '#title' => 'netFORUM Types',
456 '#type' => 'checkboxes',
457 '#options' => $categories,
458 '#default_value' => $netforum_auth_roles[$rid],
459 );
460 }
461
462 $form['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration') );
463 $form['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset to defaults') );
464
465 if (!empty($_POST) && form_get_errors()) {
466 drupal_set_message(t('The settings have not been saved because of the errors.'), 'error');
467 }
468 $form['#base'] = 'system_settings_form';
469
470 return $form;
471 }
472
473 function netforum_auth_role_settings_submit($form_id, $form_values) {
474 $netforum_auth_roles = array();
475
476 foreach (array_diff_key(user_roles(), netforum_auth_ignore_roles()) as $rid => $role) {
477 $element = 'netforum_auth_roles_'. $rid;
478 $netforum_auth_roles[$rid] = array();
479 if (isset($form_values[$element]) && is_array($form_values[$element])) {
480 foreach ($form_values[$element] as $key => $check_val) {
481 if ((string)$key == (string)$check_val) {
482 $netforum_auth_roles[$rid][] = $key;
483 }
484 }
485 }
486 }
487
488 variable_set('netforum_auth_roles', $netforum_auth_roles);
489 drupal_set_message(t('The configuration options have been saved.'));
490 }
491
492 /**
493 * Validate the sso logout url option
494 */
495 function netforum_valid_sso_logout($form = null) {
496 if ($form == null) {
497 return;
498 }
499 $url = $form['#post']['netforum_auth_sso_logout_url'];
500 if (strtolower(trim($url)) != '<drupal>' && valid_url($url, true) == false) {
501 form_set_error('netforum_auth_sso_logout_url', t('Valid sso logout URL required'));
502 }
503 }
504
505 /**
506 * clear the menu cache and redirect to netforum settings
507 *
508 */
509 function netforum_auth_cache_clear() {
510 cache_clear_all('*', 'cache_menu', TRUE);
511 drupal_set_message(t('Menu cache cleared.'));
512 drupal_goto('admin/settings/netforum');
513 }
514
515 /**
516 * Confirm that this site is available for shared sso and that the shared secret is valid
517 *
518 */
519 function netforum_auth_shared_sso_check($token = '') {
520 $passed_check = false;
521 if (variable_get('netforum_auth_shared_sso', 0) != 0 && variable_get('netforum_auth_shared_sso_secret', '') != '') {
522 $internal_token = md5("netFORUMAUTHINTERNAL". variable_get('netforum_auth_shared_sso_secret', ''));
523 if ($internal_token == $token) {
524 $passed_check = true;
525 }
526 }
527 print "<checkresult>". $passed_check ."</checkresult>";
528 exit;
529 }
530
531
532 /**
533 * Find the username and password for the individual and log them into eweb
534 *
535 * We use two tokens to save a DB/xweb hit where not necessary, one for the password one for the cst_id
536 *
537 * Enterprise only.
538 */
539 function netforum_auth_shared_sso_login($cstid = '', $cstid_token = '', $pw_token = '') {
540 $allow_logins = variable_get('netforum_auth_shared_sso', 0);
541 $shared_secret = variable_get('netforum_auth_shared_sso_secret', '');
542 if ($allow_logins != 0 && $shared_secret != '' && $cstid != '' && $cstid_token != '') {
543 $cstid_internal_token = _netforum_auth_make_cstid_token($cstid);
544 if ($cstid_internal_token == $cstid_token) {
545 $account = new stdClass();
546 $res = db_query("SELECT cst_key, cst_id, cst_web_password FROM {users} WHERE cst_id = '%s'", $cstid);
547 if (db_num_rows($res) >= 0) {
548 while ($account = db_fetch_object($res)) {
549 if (_netforum_auth_match_password_token($account->cst_web_password, $pw_token) ) {
550 $account->name = $account->cst_id;
551 $found_user = true;
552 break;
553 }
554 }
555 }
556 else {
557 $arguments = array(
558 'szObjectName' => "Customer",
559 'szColumnList' => "cst_id, cst_web_password",
560 'szWhereClause' => "cst_id = \'". $cstid ."\'",
561 'szOrderBy' => "",
562 );
563 $response = netforum_xweb_request('GetQuery', $arguments);
564 if ($response && $response->attributes()->recordReturn > 0) {
565 foreach ($response->CustomerObject as $cst) {
566 if (_netforum_auth_match_password_token($cst->cst_web_password, $pw_token)) {
567 $found_user = true;
568 $account->cst_key = (string)$cst->cst_key;
569 $account->cst_web_password = (string)$cst->cst_web_password;
570 $account->cst_id = $cstid;
571 $account->name = $cstid;
572 }
573 }
574 }
575 }
576 if ($found_user === true) {
577 netforum_auth_eweb_sso_login($account);
578 }
579 }
580 }
581 print "<!--";
582 exit;
583 }
584
585 /**
586 * Take a customer ID and turn it into a unique token
587 *
588 */
589 function _netforum_auth_make_cstid_token($cstid = ''){
590 return md5(variable_get('netforum_auth_shared_sso_secret', '') ."evensalt". $_SERVER['REMOTE_ADDR'] .'FreeTokensforall!'. $cstid ."nfautthcstid");
591 }
592
593 /**
594 * Take a password and turn it into a unique token
595 *
596 */
597 function _netforum_auth_make_password_token($cst_web_password = '', $timestamp = ''){
598 if ($timestamp == '') {
599 $timestamp = time();
600 }
601 return md5('nfauthpwstr'. $cst_web_password .'oddsalt'. $_SERVER['REMOTE_ADDR'] .'goodfornow'. $timestamp ."looooongstringsalt!". variable_get('netforum_auth_shared_sso_secret', ''));
602 }
603
604 /**
605 * Find out if the password matches a recently generated token
606 *
607 * for the tokens to match they must be generated within 20 seconds of each other and requsted by the same host/ip address
608 */
609 function _netforum_auth_match_password_token($cst_web_password, $token) {
610 $seconds_back = 10;
611 $timestamp = time();
612 $match = false;
613 for ($i = 0; $i <= $seconds_back ; $i++) {
614 $internal_token = _netforum_auth_make_password_token($cst_web_password, ($timestamp - $i));
615 if ($internal_token == $token) {
616 $match = true;
617 break;
618 }
619 }
620 return $match;
621 }
622
623 /**
624 * Implementation of hook_auth()
625 *
626 * uses xWeb to get an auth token, returns true if the token is set,
627 * false if the token is empty (eg, all zeroes)
628 *
629 * Also intercepts all login requests with an email address associated and
630 * authenticates against netFORUM. Local users should have no @ sign in their
631 * username
632 */
633 function netforum_authentication_auth($username, $password, $server) {
634 global $_netforum_auth_password_plain;
635 $_netforum_auth_password_plain = $password;
636 if ($server != '') {
637 $username .= "@$server";
638 }
639
640 //If we are using OD skip the rest of our function and let the OD function handle it.
641 if (netforum_is_team()) {
642 return _netforum_authentication_auth_od($username, $password);
643 }
644
645 $result = netforum_xweb_request("WebLogin", array("userLoginPlain" => $username,
646 "passwordPlain" => $password,
647 "keyOverride" => ""));
648
649 if (is_null($result) || isset($result->WebLoginResult) === false ) {
650 //try local authentication juuuust in case xWeb is unavailable
651 if (_netforum_authentication_auth_local($username, $password)) {
652 return true;
653 }
654 else {
655 drupal_set_message(t('Could not communicate with authentication server, please try again in a few minutes'), 'error');
656 watchdog('netforum', t("Could not authenticate user %username because the xweb query returned a null result - is the server available and responsive?", array('%username' => $username)), WATCHDOG_ERROR);
657 return false;
658 }
659 }
660
661
662 $auth_token = $result->WebLoginResult;
663
664 if (netforum_is_empty_guid($auth_token)) {
665 return FALSE;
666 }
667 else {
668 global $_netforum_auth_token;
669 $_netforum_auth_token = $auth_token;
670 return TRUE;
671 }
672
673 }
674
675 /**
676 * Called from netforum_authentication_auth which is the implementation of hook_auth()
677 *
678 * uses xWeb On Demand to verify a login, returns true a matching username and pw,
679 * false if CheckeWebUser returns no records
680 *
681 */
682 function _netforum_authentication_auth_od($username, $password) {
683 $result = netforum_xweb_request("CheckEWebUser", array("szEmail" => $username,
684 "szPassword" => $password));
685
686 if (is_null($result) ) {
687 //try local authentication juuuust in case xWeb is unavailable
688 if (_netforum_authentication_auth_local($username, $password)) {
689 $_SESSION['_netforum_auth_eweb_pw'] = $password;
690 return true;
691 }
692 else {
693 drupal_set_message(t('Could not communicate with authentication server, please try again in a few minutes'), 'error');
694 watchdog('netforum', t("Could not authenticate user %username because the xweb query returned a null result - is the server available and responsive?", array('%username' => $username)), WATCHDOG_ERROR);
695 return false;
696 }
697 }
698
699 if ($result->attributes()->recordReturn == 0) {
700 return FALSE;
701 }
702 else {
703 global $_netforum_auth_eweb_od_user;
704 $_netforum_auth_eweb_od_user = $result->Result;
705 $_SESSION['_netforum_auth_eweb_pw'] = $password; //We'll need this if they hit a link going to the netforum site
706 return TRUE;
707 }
708 }
709
710 /**
711 * Authenticate a username using the local DB cache
712 *
713 */
714 function _netforum_authentication_auth_local($username, $password) {
715 $result = db_query("SELECT uid FROM {users} WHERE name='%s' AND cst_web_password = '%s'", $username, netforum_auth_hash_pw($password));
716 if (db_num_rows($result) > 0) {
717 return true;
718 }
719 else {
720 return false;
721 }
722 }
723
724 /**
725 * Implementation of hook_info()
726 *
727 */
728 function netforum_authentication_info($field = 0) {
729 $info['name'] = 'netFORUM';
730 $info['protocol'] = 'SOAP';
731
732 if ($field) {
733 return $info[$field];
734 }
735 else {
736 return $info;
737 }
738 }
739
740 /**
741 * Implementation of hook_user()
742 *
743 */
744 function netforum_authentication_user($op, &$edit, &$account, $category = NULL) {
745 global $user;
746 switch ($op) {
747 case 'load':
748 //we do a lot on load because whenever a user is displayed, logged in, or worked with they should have the most up to date information,
749 //which means getting all of their data from netFORUM
750 if (!netforum_is_empty_guid($account->cst_key)) {
751 if ($_SESSION['netforum_auth_skip_info_sync'] !== true) {
752 global $_netforum_auth_already_checked_roles;
753 $ignore_roles = netforum_auth_ignore_roles();
754 $new_info = array();
755
756
757 if ($_netforum_auth_already_checked_roles !== true) {
758 $netforum_roles = netforum_auth_map_user_roles($account);
759 //If there is no response from xweb we get an empty array, this is to prevent us from mistaing no response for no roles or entering an infinite update loop
760 if (netforum_auth_fresh_user_categories() === true) {
761 //these are the roles that are checked off on the site's user edit page, not set via netForum.
762 $sticky_roles = array();
763 //no, not the delicious kind of sticky roles.
764
765 if (is_array($account->netforum_auth_admin_set_roles)) {
766 $sticky_roles = array_diff_key($account->netforum_auth_admin_set_roles, $netforum_roles); //take out the roles that are being set by netforum
767 $sticky_roles = array_diff_key($sticky_roles, $ignore_roles); //take out the roles like authenticated user
768
769 if ($sticky_roles != $account->netforum_auth_admin_set_roles) {
770 //if the netforum roles now include a role that was sticky it's removed from the sticky roles and is no longer given to the user by the sticky roles.
771 //That way, when netforum stops returning that role, then it will also dissapear from their account.
772 $new_info['netforum_auth_admin_set_roles'] = $sticky_roles;
773 }
774 }
775
776 //Now we need to look at the current user roles (from the db) and compare them to the combined netforum roles and sticky roles.
777 //if they are different, update the roles and save them to the database.
778 $combined_roles = array();
779 foreach ($sticky_roles as $k => $v) {
780 $combined_roles[$k] = $v;
781 }
782 foreach ($netforum_roles as $k => $v){
783 $combined_roles[$k] = $v;
784 }
785
786 //just to make sure that that the roles we're comparing are the actual values from site, eg if the name of the role was changed,
787 //we use array_walk to replace the values of our combined roles array with the freshest site values based on the id
788 $site_roles = user_roles(true);
789 array_walk($combined_roles, create_function('&$v, $k, &$roles', '$v = $roles[$k];'), $site_roles);
790
791 //If there is some change, store them in the database. This way, if xWeb is unavailable we can still give the user appropriate permissions
792 if ($combined_roles != array_diff_key($account->roles, $ignore_roles)) {
793 $new_info['roles'] = $combined_roles;
794 }
795 $_netforum_auth_already_checked_roles = true;
796 } //end ofthe roles checking and assigning
797 }
798
799 $cst_fields = netforum_auth_user_fields();
800 $eml_field = _netforum_auth_get_email_field('Customer');
801 if (in_array($eml_field, $cst_fields) === false) {
802 $cst_fields[] = $eml_field; //make sure we grab the email address
803 }
804 if (in_array('cst_web_force_password_change', $cst_fields) === false) {
805 $cst_fields[] = 'cst_web_force_password_change'; //make sure we check to see if they should change their pw
806 }
807
808 //Now that we know what fields we want, go get them
809 $cst_obj = false;
810 if (netforum_is_enterprise()) { //Enterprise only gets the query
811 //check to see if any of the customer fields are different from the information in the DB
812 $query = Array('szObjectName' => 'Customer',
813 'szColumnList' => implode(", ", $cst_fields),
814 'szWhereClause' => "cst_key = '". $account->cst_key ."'",
815 'szOrderBy' => '');
816 $response = netforum_xweb_request('GetQuery', $query, '3 seconds'); //do not look for cached results to this query longer than this request
817 if ($response && $response->attributes()->recordReturn == 1) {
818 $cst_obj = $response->CustomerObject;
819 }
820 }
821 else { //This is the NF OD Section
822 //For netFORUM Team/Pro/On Demand use the data returned from CheckEWebUser
823 global $_netforum_auth_eweb_od_user;
824 if (isset($_netforum_auth_eweb_od_user)) {
825 $cst_obj = $_netforum_auth_eweb_od_user;
826 }
827 else {
828 $query = Array('szObjectName' => 'Customer',
829 'sz' => implode(", ", $cst_fields),
830 'szWhereClause' => "cst_key = '". $account->cst_key ."'",
831 'szOrderBy' => '');
832
833
834 $arguments = array(
835 'szObjectName' => "Customer",
836 'szObjectKey' => $account->cst_key,
837 );
838 $response = netforum_xweb_request('GetFacadeObject', $arguments, '3 seconds'); //do not look for cached results to this query longer than this request
839 if ($response && $response->attributes()->recordReturn == 1) {
840 $cst_obj = $response->CustomerObject;
841 }
842 }
843 }
844
845 // If either of the above calls to get user data succeeded, load up the account
846 if ($cst_obj) {
847 $email = (string)$cst_obj->{$eml_field};
848 if ($account->mail != $email ) {
849 $new_info['mail'] = $email;
850 }
851
852 if ((int)$cst_obj->cst_web_force_password_change == 1) {
853 global $_netforum_auth_must_change_pw;
854 $_netforum_auth_must_change_pw = true;
855 }
856
857 foreach ($cst_fields as $field) {
858 $field_value = (string)$cst_obj->{$field};
859 if (empty($field) === false && $account->{$field} != $field_value) {
860 $new_info[$field] = $field_value;
861 }
862 }
863 }
864
865 if (count($new_info) > 0) {
866 user_save($account, $new_info);
867 }
868 }
869 }
870 break;
871
872 case 'login':
873 global $_netforum_auth_must_change_pw;
874
875 if ($_netforum_auth_must_change_pw === true) {
876 $current_path = isset($_GET['q']) ? $_GET['q'] : '';
877 $edit_path = 'user/'. $account->uid .'/edit';
878 if ($current_path != $edit_path && ($user->uid == $account->uid)) {
879 drupal_set_message('Your password needs to be reset, please do so now by clicking '. l('here', $edit_path), 'error');
880 }
881 }
882 //Shared SSO is Enterprise only
883 if (netforum_is_enterprise() && netforum_is_empty_guid($account->cst_key) === false) {
884
885 netforum_auth_eweb_sso_login();
886
887 $shared_sso_url = variable_get('netforum_auth_shared_sso_url', '');
888 if ($shared_sso_url != '') {
889 if (substr($shared_sso_url, -1) != "/") {
890 $shared_sso_url .= "/";
891 }
892 $cstid_token = _netforum_auth_make_cstid_token($account->cst_id);
893 $pw_token = _netforum_auth_make_password_token($account->cst_web_password);
894 $link_attributes = array('rel' => 'stylesheet', 'type' => 'text/css',
895 'href' => $shared_sso_url ."user/netforum_sso_share/". $account->cst_id ."/". $cstid_token ."/". $pw_token);
896 $_SESSION['netforum_auth_link'] = $link_attributes;
897 }
898 }
899 break;
900
901 case 'logout': //Enterprise only
902 if (netforum_is_empty_guid($account->cst_key) === false) {
903 netforum_auth_eweb_sso_logout();
904 unset($_SESSION['_netforum_auth_eweb_pw']);
905 }
906 break;
907
908 case 'insert':
909 global $_netforum_auth_token, $_netforum_auth_cst_key, $_netforum_auth_eweb_od_user;
910
911 $nf_user_data = array(); //The goal is to populate this so we can add in the new user
912 $eml_field = _netforum_auth_get_email_field('Customer');
913 $needed_columns = array_unique(array('cst_type', 'cst_web_password', 'cst_id') + netforum_auth_user_fields());
914
915
916 //For Enterprise netforum if we have the customer key or can get it
917 if (netforum_is_empty_guid($_netforum_auth_token) === false || netforum_is_empty_guid($_netforum_auth_cst_key) === false) {
918 if (netforum_is_empty_guid($_netforum_auth_token) === false) {
919 $result = netforum_xweb_request("WebValidate", array("authenticationToken" => $_netforum_auth_token));
920 if (is_null($result) || isset($result->WebValidateResult) === false ) {
921 drupal_set_message(t('Could not fetch netFORUM information for new user'), 'error');
922 break;
923 }
924 else {
925 $_netforum_auth_cst_key = (string)$result->WebValidateResult;
926 $_netforum_auth_token = null; //unset this so we don't keep calling xweb
927 }
928 }
929
930 if (netforum_is_empty_guid($_netforum_auth_cst_key) === false) {
931
932 $query = Array('szObjectName' => 'Customer',
933 'szColumnList' => $eml_field .", ". implode($needed_columns, ", "),
934 'szWhereClause' => "cst_key = '". $_netforum_auth_cst_key ."'",
935 'szOrderBy' => '');
936 $response = netforum_xweb_request('GetQuery', $query);
937 if (isset($response) && $response->attributes()->recordReturn == 1) {
938 $cst_obj = $response->CustomerObject;
939 $nf_user_data = Array(
940 'cst_key' => $_netforum_auth_cst_key,
941 'mail' => (string)$cst_obj->{$eml_field},
942 );
943 foreach($needed_columns as $col) {
944 $nf_user_data[$col] = (string)$cst_obj->{$col};
945 }
946 $nf_user_data['cst_web_password'] = (string)$cst_obj->cst_web_password;
947 }
948 else {
949 $nf_user_data['cst_key'] = $_netforum_auth_cst_key;
950 }
951 }
952 }
953
954 //For netFORUM Team/Pro/On Demand use the data returned from CheckEWebUser
955 if (isset($_netforum_auth_eweb_od_user)) {
956 $nf_user_data = Array(
957 'cst_key' => (string)$_netforum_auth_eweb_od_user->cst_key,
958 'mail' => (string)$_netforum_auth_eweb_od_user->{$eml_field},
959 );
960 foreach($needed_columns as $col) {
961 $nf_user_data[$col] = (string)$_netforum_auth_eweb_od_user->{$col};
962 }
963 $nf_user_data['cst_web_password'] = (string)$_netforum_auth_eweb_od_user->cst_web_password;
964 }
965
966
967 if ($nf_user_data) {
968 user_save($account, $nf_user_data);
969 }
970
971
972 break;
973
974 case 'update':
975 if ( netforum_is_empty_guid($account->cst_key) === false) {
976 if (netforum_is_enterprise()) { //We only push data back to Enterprise netforum
977 $eml_field = _netforum_auth_get_email_field('Customer');
978 //this array keeps key => value pairs to correspond to the netFORUM information to be updated.
979 $nf_variables = array();
980 if (strtolower(variable_get('netforum_auth_user_editing', '<drupal>')) == '<drupal>') {
981 if (isset($edit['mail']) && $edit['mail'] != '' && $edit['mail'] != $account->mail) {
982 $nf_variables[$eml_field] = $edit['mail'];
983 }
984 if (isset($edit['pass']) && empty($edit['pass']) === false) {
985 $nf_variables['cst_web_password'] = $edit['pass'];
986 $nf_variables['cst_web_force_password_change'] = 0;
987 //Save cst_web_password because we use it if the user is logging in and xWeb is not available.
988 $edit['cst_web_password'] = netforum_auth_hash_pw($edit['pass']);
989 }
990 if (isset($edit['name']) && $edit['name'] != '' && $edit['name'] != $account->name) {
991 $nf_variables['cst_web_login'] = $edit['name'];
992 }
993 }
994
995 if (count($nf_variables) > 0) {
996 $success = false; //don't assume it worked by default, this is set on success below
997 if ($account->cst_type == 'Individual') { //this way customers and individuals can log in and edit info. Beware when setting variables above that they are cst_type agnostic
998 $data_node = netforum_ind_info_parameters($nf_variables);
999 $response = netforum_xweb_request('SetIndividualInformation', array('IndividualKey' => $account->cst_key, 'oUpdateNode' => $data_node));
1000 if (isset($response) && $response->attributes()->recordReturn == 1 && $response->IndividualObject->ind_cst_key == $account->cst_key) {
1001 $success = true;
1002 }
1003 }
1004 elseif ($account->cst_type == 'Organization') {
1005 $data_node = netforum_org_info_parameters($nf_variables);
1006 $response = netforum_xweb_request('SetOrganizationInformation', array('OrganizationKey' => $account->cst_key, 'oUpdateNode' => $data_node));
1007 if ( isset($response) && $response->attributes()->recordReturn == 1 && $response->OrganizationObject->org_cst_key == $account->cst_key) {
1008 $success = true;
1009 }
1010 }
1011
1012 if ($success === false) {
1013 drupal_set_message(t('Information update failed, please try again in a few minutes.'), 'error');
1014 watchdog('netforum', t("Could not update user information in netFORUM for %name", array('%name' => $account->name)), WATCHDOG_ERROR);
1015 $path = isset($_GET['q']) ? $_GET['q'] : '';
1016 $query = drupal_query_string_encode($_GET, array('q'));
1017 if ($query != '') {
1018 $path .= '?'. $query;
1019 }
1020 drupal_goto($path);
1021 }
1022 else {
1023 $_SESSION['netforum_auth_skip_info_sync'] = true;
1024 }
1025 }
1026 }
1027
1028 if (isset($edit['roles']) ) {
1029 //keep track of any roles set in case it was set by an admin. If the admin added a role that doesn't get set via netforum remember that and
1030 //add / save it in when the user is loaded. If the role is later something gets set for this user via netforum, then when it is removed (ie, no longer
1031 //set via netforum) it will also be removed from the user record. That means you can make the website override netforum, but if netforum later gives that
1032 //permission it will be taken away when netforum also takes it away.
1033
1034 //what's the phrase? Store em all and let the load sort it out?
1035 $edit['netforum_auth_admin_set_roles'] = $edit['roles'];
1036 }
1037 }
1038
1039 break;
1040
1041 case 'after_update':
1042 if (isset($_SESSION['netforum_auth_skip_info_sync']) && $_SESSION['netforum_auth_skip_info_sync'] === true) {
1043 unset($_SESSION['netforum_auth_skip_info_sync']);
1044 }
1045 break;
1046
1047 default:
1048 break;
1049 }
1050 }
1051
1052 /**
1053 * Fetch the netforum criteria for assigning users to groups
1054 *
1055 * This function should return an associative array of netFORUM keys and
1056 * descrptions that will be used to determine what drupal roles map to
1057 * what netFORUM roles. For example, member types or committes.
1058 *
1059 * To assign users to groups based on something other than membership create
1060 * a module that implements hook_netforum_auth_categories.
1061 *
1062 * If you use netFORUM Team or Pro use hook_netforum_auth_od_categories
1063 *
1064 * Remember to cast all associatve array keys to strings before storing in the array
1065 *
1066 * @return
1067 * Returns an associative array of unique keys corresponding to descriptions
1068 */
1069 function netforum_auth_categories() {
1070 $categories = array();
1071