PluginProbe
Shibboleth / 1.4
Shibboleth v1.4
trunk 1.0 1.1 1.2 1.3 1.4 1.6 1.7 1.8 1.8.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.2 2.2.1 2.2.2 2.3 2.4 2.4.1 2.4.2 2.4.3 2.5.0 2.5.1 All 28 releases
shibboleth / shibboleth.php

shibboleth.php in Shibboleth 1.4, at shibboleth.php

494 lines 15.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Shibboleth
4 Plugin URI: http://wordpress.org/extend/plugins/shibboleth
5 Description: Easily externalize user authentication to a <a href="http://shibboleth.internet2.edu">Shibboleth</a> Service Provider
6 Author: Will Norris, mitcho (Michael 芳貴 Erlewine)
7 Version: 1.4
8 License: Apache 2 (http://www.apache.org/licenses/LICENSE-2.0.html)
9 */
10
11 define ( 'SHIBBOLETH_PLUGIN_REVISION', preg_replace( '/\$Rev: (.+) \$/', '\\1',
12 '$Rev$') ); // this needs to be on a separate line so that svn:keywords can work its magic
13
14
15 // run activation function if new revision of plugin
16 $shibboleth_plugin_revision = shibboleth_get_option('shibboleth_plugin_revision');
17 if ($shibboleth_plugin_revision === false || SHIBBOLETH_PLUGIN_REVISION != $shibboleth_plugin_revision) {
18 add_action('admin_init', 'shibboleth_activate_plugin');
19 }
20
21
22 /**
23 * Activate the plugin. This registers default values for all of the
24 * Shibboleth options and attempts to add the appropriate mod_rewrite rules to
25 * WordPress's .htaccess file.
26 */
27 function shibboleth_activate_plugin() {
28 if ( function_exists('switch_to_blog') ) switch_to_blog($GLOBALS['current_site']->blog_id);
29
30 shibboleth_add_option('shibboleth_login_url', get_option('home') . '/Shibboleth.sso/Login');
31 shibboleth_add_option('shibboleth_default_login', false);
32 shibboleth_add_option('shibboleth_logout_url', get_option('home') . '/Shibboleth.sso/Logout');
33
34 $headers = array(
35 'username' => array( 'name' => 'eppn', 'managed' => false),
36 'first_name' => array( 'name' => 'givenName', 'managed' => true),
37 'last_name' => array( 'name' => 'sn', 'managed' => true),
38 'nickname' => array( 'name' => 'eppn', 'managed' => true),
39 'display_name' => array( 'name' => 'displayName', 'managed' => true),
40 'email' => array( 'name' => 'mail', 'managed' => true),
41 );
42 shibboleth_add_option('shibboleth_headers', $headers);
43
44 $roles = array(
45 'administrator' => array(
46 'header' => 'entitlement',
47 'value' => 'urn:mace:example.edu:entitlement:wordpress:admin',
48 ),
49 'author' => array(
50 'header' => 'affiliation',
51 'value' => 'faculty',
52 ),
53 // TODO: this could likely do strange things if WordPress has an actual role named 'default'
54 'default' => 'subscriber',
55 );
56 shibboleth_add_option('shibboleth_roles', $roles);
57
58 shibboleth_add_option('shibboleth_update_roles', true);
59
60 shibboleth_insert_htaccess();
61
62 shibboleth_migrate_old_data();
63
64 shibboleth_update_option('shibboleth_plugin_revision', SHIBBOLETH_PLUGIN_REVISION);
65
66 if ( function_exists('restore_current_blog') ) restore_current_blog();
67 }
68 register_activation_hook('shibboleth/shibboleth.php', 'shibboleth_activate_plugin');
69
70
71 /**
72 * Cleanup certain plugins options on deactivation.
73 */
74 function shibboleth_deactivate_plugin() {
75 shibboleth_remove_htaccess();
76 }
77 register_deactivation_hook('shibboleth/shibboleth.php', 'shibboleth_deactivate_plugin');
78
79
80 /**
81 * Migrate old data to newer formats.
82 */
83 function shibboleth_migrate_old_data() {
84
85 // new header format, allowing each header to be marked as 'managed' individually
86 $managed = shibboleth_get_option('shibboleth_update_users');
87 $headers = shibboleth_get_option('shibboleth_headers');
88 $updated = false;
89
90 foreach ($headers as $key => $value) {
91 if ( is_string($value) ) {
92 $headers[$key] = array(
93 'name' => $value,
94 'managed' => $managed,
95 );
96 $updated = true;
97 }
98 }
99
100 if ( $updated ) {
101 shibboleth_update_option('shibboleth_headers', $headers);
102 }
103 shibboleth_delete_option('shibboleth_update_users');
104
105 }
106
107 /**
108 * Load Shibboleth admin hooks only on admin page loads.
109 *
110 * 'admin_init' is actually called *after* 'admin_menu', so we have to hook in
111 * to the 'init' action for this.
112 */
113 function shibboleth_admin_hooks() {
114 if ( defined('WP_ADMIN') && WP_ADMIN === true ) {
115 require_once dirname(__FILE__) . '/options-admin.php';
116 require_once dirname(__FILE__) . '/options-user.php';
117 }
118 }
119 add_action('init', 'shibboleth_admin_hooks');
120
121
122 /**
123 * Check if a Shibboleth session is active.
124 *
125 * @return boolean if session is active
126 * @uses apply_filters calls 'shibboleth_session_active' before returning final result
127 */
128 function shibboleth_session_active() {
129 $active = false;
130
131 $session_headers = array('Shib-Session-ID', 'HTTP_SHIB_IDENTITY_PROVIDER');
132 foreach ($session_headers as $header) {
133 if ( array_key_exists($header, $_SERVER) && !empty($_SERVER[$header]) ) {
134 $active = true;
135 break;
136 }
137 }
138
139 $active = apply_filters('shibboleth_session_active', $active);
140 return $active;
141 }
142
143
144 /**
145 * Authenticate the user using Shibboleth. If a Shibboleth session is active,
146 * use the data provided by Shibboleth to log the user in. If a Shibboleth
147 * session is not active, redirect the user to the Shibboleth Session Initiator
148 * URL to initiate the session.
149 */
150 function shibboleth_authenticate($user, $username, $password) {
151 if ( shibboleth_session_active() ) {
152 return shibboleth_authenticate_user();
153 } else {
154 $initiator_url = shibboleth_session_initiator_url( $_REQUEST['redirect_to'] );
155 wp_redirect($initiator_url);
156 exit;
157 }
158 }
159
160
161 /**
162 * When wp-login.php is loaded with 'action=shibboleth', hook Shibboleth
163 * into the WordPress authentication flow.
164 */
165 function shibboleth_login_form_shibboleth() {
166 add_filter('authenticate', 'shibboleth_authenticate', 10, 3);
167 }
168 add_action('login_form_shibboleth', 'shibboleth_login_form_shibboleth');
169
170
171 /**
172 * If a Shibboleth user requests a password reset, and the Shibboleth password
173 * reset URL is set, redirect the user there.
174 */
175 function shibboleth_retrieve_password( $user_login ) {
176 $password_reset_url = shibboleth_get_option('shibboleth_password_reset_url');
177
178 if ( !empty($password_reset_url) ) {
179 $user = get_userdatabylogin($user_login);
180 if ( $user && get_usermeta($user->ID, 'shibboleth_account') ) {
181 wp_redirect($password_reset_url);
182 exit;
183 }
184 }
185 }
186 add_action('retrieve_password', 'shibboleth_retrieve_password');
187
188
189 /**
190 * If Shibboleth is the default login method, add 'action=shibboleth' to the
191 * WordPress login URL.
192 */
193 function shibboleth_login_url($login_url) {
194 if ( shibboleth_get_option('shibboleth_default_login') ) {
195 $login_url = add_query_arg('action', 'shibboleth', $login_url);
196 }
197
198 return $login_url;
199 }
200 add_filter('login_url', 'shibboleth_login_url');
201
202
203 /**
204 * If the Shibboleth logout URL is set and the user has an active Shibboleth
205 * session, log the user out of Shibboleth after logging them out of WordPress.
206 */
207 function shibboleth_logout() {
208 $logout_url = shibboleth_get_option('shibboleth_logout_url');
209
210 if ( !empty($logout_url) && shibboleth_session_active() ) {
211 wp_redirect($logout_url);
212 exit;
213 }
214 }
215 add_action('wp_logout', 'shibboleth_logout', 20);
216
217
218 /**
219 * Generate the URL to initiate Shibboleth login.
220 *
221 * @param string $redirect the final URL to redirect the user to after all login is complete
222 * @return the URL to direct the user to in order to initiate Shibboleth login
223 * @uses apply_filters() Calls 'shibboleth_session_initiator_url' before returning session intiator URL
224 */
225 function shibboleth_session_initiator_url($redirect = null) {
226
227 // first build the target URL. This is the WordPress URL the user will be returned to after Shibboleth
228 // is done, and will handle actually logging the user into WordPress using the data provdied by Shibboleth
229 if ( function_exists('switch_to_blog') ) switch_to_blog($GLOBALS['current_site']->blog_id);
230 $target = site_url('wp-login.php');
231 if ( function_exists('restore_current_blog') ) restore_current_blog();
232
233 $target = add_query_arg('action', 'shibboleth', $target);
234 if ( !empty($redirect) ) {
235 $target = add_query_arg('redirect_to', urlencode($redirect), $target);
236 }
237
238 // now build the Shibboleth session initiator URL
239 $initiator_url = shibboleth_get_option('shibboleth_login_url');
240 $initiator_url = add_query_arg('target', urlencode($target), $initiator_url);
241
242 $initiator_url = apply_filters('shibboleth_session_initiator_url', $initiator_url);
243
244 return $initiator_url;
245 }
246
247
248 /**
249 * Authenticate the user based on the current Shibboleth headers.
250 *
251 * If the data available does not map to a WordPress role (based on the
252 * configured role-mapping), the user will not be allowed to login.
253 *
254 * If this is the first time we've seen this user (based on the username
255 * attribute), a new account will be created.
256 *
257 * Known users will have their profile data updated based on the Shibboleth
258 * data present if the plugin is configured to do so.
259 *
260 * @return WP_User|WP_Error authenticated user or error if unable to authenticate
261 */
262 function shibboleth_authenticate_user() {
263 $shib_headers = shibboleth_get_option('shibboleth_headers');
264
265 // ensure user is authorized to login
266 $user_role = shibboleth_get_user_role();
267
268 if ( empty($user_role) ) {
269 return new WP_Error('no_access', __('You do not have sufficient access.'));
270 }
271
272 $username = $_SERVER[$shib_headers['username']['name']];
273 $user = new WP_User($username);
274
275 if ( $user->ID ) {
276 if ( !get_usermeta($user->ID, 'shibboleth_account') ) {
277 // TODO: what happens if non-shibboleth account by this name already exists?
278 //return new WP_Error('invalid_username', __('Account already exists by this name.'));
279 }
280 }
281
282 // create account if new user
283 if ( !$user->ID ) {
284 $user = shibboleth_create_new_user($username);
285 }
286
287 if ( !$user->ID ) {
288 $error_message = 'Unable to create account based on data provided.';
289 if (defined('WP_DEBUG') && WP_DEBUG) {
290 $error_message .= '<!-- ' . print_r($_SERVER, true) . ' -->';
291 }
292 return new WP_Error('missing_data', $error_message);
293 }
294
295 // update user data
296 update_usermeta($user->ID, 'shibboleth_account', true);
297 shibboleth_update_user_data($user->ID);
298 if ( shibboleth_get_option('shibboleth_update_roles') ) {
299 $user->set_role($user_role);
300 do_action( 'shibboleth_set_user_roles', $user );
301 }
302
303 return $user;
304 }
305
306
307 /**
308 * Create a new WordPress user account, and mark it as a Shibboleth account.
309 *
310 * @param string $user_login login name for the new user
311 * @return object WP_User object for newly created user
312 */
313 function shibboleth_create_new_user($user_login) {
314 if ( empty($user_login) ) return null;
315
316 // create account and flag as a shibboleth acount
317 require_once( ABSPATH . WPINC . '/registration.php' );
318 $user_id = wp_insert_user(array('user_login'=>$user_login));
319 $user = new WP_User($user_id);
320 update_usermeta($user->ID, 'shibboleth_account', true);
321
322 // always update user data and role on account creation
323 shibboleth_update_user_data($user->ID, true);
324 $user_role = shibboleth_get_user_role();
325 $user->set_role($user_role);
326 do_action( 'shibboleth_set_user_roles', $user );
327
328 return $user;
329 }
330
331
332 /**
333 * Get the role the current user should have. This is determined by the role
334 * mapping configured for the plugin, and the Shibboleth headers present at the
335 * time of login.
336 *
337 * @return string the role the current user should have
338 * @uses apply_filters() Calls 'shibboleth_roles' after retrieving shibboleth_roles array
339 * @uses apply_filters() Calls 'shibboleth_user_role' before returning final user role
340 */
341 function shibboleth_get_user_role() {
342 global $wp_roles;
343 if ( !$wp_roles ) $wp_roles = new WP_Roles();
344
345 $shib_roles = apply_filters('shibboleth_roles', shibboleth_get_option('shibboleth_roles'));
346 $user_role = $shib_roles['default'];
347
348 foreach ( $wp_roles->role_names as $key => $name ) {
349 $role_header = $shib_roles[$key]['header'];
350 $role_value = $shib_roles[$key]['value'];
351
352 if ( empty($role_header) || empty($role_value) ) continue;
353
354 $values = split(';', $_SERVER[$role_header]);
355 if ( in_array($role_value, $values) ) {
356 $user_role = $key;
357 break;
358 }
359 }
360
361 $user_role = apply_filters('shibboleth_user_role', $user_role);
362
363 return $user_role;
364 }
365
366
367 /**
368 * Get the user fields that are managed by Shibboleth.
369 *
370 * @return Array user fields managed by Shibboleth
371 */
372 function shibboleth_get_managed_user_fields() {
373 $headers = shibboleth_get_option('shibboleth_headers');
374 $managed = array();
375
376 foreach ($headers as $name => $value) {
377 if ( $value['managed'] ) {
378 $managed[] = $name;
379 }
380 }
381
382 return $managed;
383 }
384
385
386 /**
387 * Update the user data for the specified user based on the current Shibboleth headers. Unless
388 * the 'force_update' parameter is true, only the user fields marked as 'managed' fields will be
389 * updated.
390 *
391 * @param int $user_id ID of the user to update
392 * @param boolean $force_update force update of user data, regardless of 'managed' flag on fields
393 * @uses apply_filters() Calls 'shibboleth_user_*' before setting user attributes,
394 * where '*' is one of: login, nicename, first_name, last_name,
395 * nickname, display_name, email
396 */
397 function shibboleth_update_user_data($user_id, $force_update = false) {
398 require_once( ABSPATH . WPINC . '/registration.php' );
399
400 $shib_headers = shibboleth_get_option('shibboleth_headers');
401
402 $user_fields = array(
403 'user_login' => 'username',
404 'user_nicename' => 'username',
405 'first_name' => 'first_name',
406 'last_name' => 'last_name',
407 'nickname' => 'nickname',
408 'display_name' => 'display_name',
409 'user_email' => 'email'
410 );
411
412 $user_data = array(
413 'ID' => $user_id,
414 );
415
416 foreach ($user_fields as $field => $header) {
417 if ( $force_update || $shib_headers[$header]['managed'] ) {
418 $filter = 'shibboleth_' . ( strpos($field, 'user_') === 0 ? '' : 'user_' ) . $field;
419 $user_data[$field] = apply_filters($filter, $_SERVER[$shib_headers[$header]['name']]);
420 }
421 }
422
423 wp_update_user($user_data);
424 }
425
426
427 /**
428 * Sanitize the nicename using sanitize_user
429 * See discussion: http://wordpress.org/support/topic/377030
430 *
431 * @since 1.4
432 */
433 add_filter( 'shibboleth_user_nicename', 'sanitize_user' );
434
435 /**
436 * Add a "Login with Shibboleth" link to the WordPress login form. This link
437 * will be wrapped in a <p> with an id value of "shibboleth_login" so that
438 * deployers can style this however they choose.
439 */
440 function shibboleth_login_form() {
441 $login_url = add_query_arg('action', 'shibboleth');
442 echo '<p id="shibboleth_login"><a href="' . $login_url . '">' . __('Login with Shibboleth', 'shibboleth') . '</a></p>';
443 }
444 add_action('login_form', 'shibboleth_login_form');
445
446
447 /**
448 * Insert directives into .htaccess file to enable Shibboleth Lazy Sessions.
449 */
450 function shibboleth_insert_htaccess() {
451 if ( got_mod_rewrite() ) {
452 $htaccess = get_home_path() . '.htaccess';
453 $rules = array('AuthType Shibboleth', 'Require Shibboleth');
454 insert_with_markers($htaccess, 'Shibboleth', $rules);
455 }
456 }
457
458
459 /**
460 * Remove directives from .htaccess file to enable Shibboleth Lazy Sessions.
461 */
462 function shibboleth_remove_htaccess() {
463 if ( got_mod_rewrite() ) {
464 $htaccess = get_home_path() . '.htaccess';
465 insert_with_markers($htaccess, 'Shibboleth', array());
466 }
467 }
468
469
470 /* Custom option functions to correctly use WPMU *_site_option functions when available. */
471 function shibboleth_get_option($key, $default = false ) {
472 return function_exists('get_site_option') ? get_site_option($key, $default) : get_option($key, $default);
473 }
474 function shibboleth_add_option($key, $value, $autoload = 'yes') {
475 if (function_exists('add_site_option')) {
476 // WordPress MU's add_site_option() is totally broken, in that it simply calls site_update_option()
477 // if a value exists instead of leaving it alone like add_option() does.
478 global $wpdb;
479 $row = $wpdb->get_row( $wpdb->prepare("SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s AND site_id = %d", $key, $wpdb->siteid) );
480 if ($row !== null) return false;
481
482 return add_site_option($key, $value);
483 } else {
484 return add_option($key, $value, '', $autoload);
485 }
486 }
487 function shibboleth_update_option($key, $value) {
488 return function_exists('update_site_option') ? update_site_option($key, $value) : update_option($key, $value);
489 }
490 function shibboleth_delete_option($key) {
491 return function_exists('delete_site_option') ? delete_site_option($key) : delete_option($key);
492 }
493
494