PluginProbe
Shibboleth / trunk
Shibboleth vtrunk
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 trunk, at shibboleth.php

1,278 lines 40.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shibboleth
4 *
5 * @package shibboleth
6 *
7 * @wordpress-plugin
8 * Plugin Name: Shibboleth
9 * Plugin URI: https://wordpress.org/plugins/shibboleth/
10 * Description: Easily externalize user authentication to a <a href="https://www.incommon.org/software/shibboleth/">Shibboleth</a> Service Provider
11 * Author: Michael McNeill, Jonathan Champ, Michael Erlewine, Will Norris
12 * Version: 2.5.4
13 * Requires PHP: 5.6
14 * Requires at least: 4.3
15 * License: Apache-2.0
16 * Text Domain: shibboleth
17 */
18
19 defined( 'ABSPATH' ) || exit;
20
21 define( 'SHIBBOLETH_MINIMUM_WP_VERSION', '4.3' );
22 define( 'SHIBBOLETH_MINIMUM_PHP_VERSION', '5.6' );
23 define( 'SHIBBOLETH_PLUGIN_VERSION', '2.5.4' );
24
25 /**
26 * Determine if this is a new install or upgrade and, if so, run the
27 * shibboleth_activate_plugin() function.
28 *
29 * @since 1.0
30 */
31 $shibboleth_plugin_version = get_site_option( 'shibboleth_plugin_version', '0' );
32 if ( SHIBBOLETH_PLUGIN_VERSION !== $shibboleth_plugin_version ) {
33 add_action( 'admin_init', 'shibboleth_activate_plugin' );
34 }
35
36 /**
37 * Determine if a constant is defined. If it is, return the value of the constant.
38 * If it isn't, return the value from get_site_option().
39 * If you'd like to known whether or not the value was obtained as a constant,
40 * set $return_array to true and check the resulting array.
41 *
42 * @since 2.1
43 * @param string $option Option identifier.
44 * @param bool $default_value Default value.
45 * @param bool $unused Deprecated parameter; unused.
46 * @param bool $return_array If you want the constant and value returned as an array.
47 * @return mixed
48 */
49 function shibboleth_getoption( $option, $default_value = false, $unused = false, $return_array = false ) {
50 // If a constant is defined with the provided option name, get the value of the constant.
51 if ( defined( strtoupper( $option ) ) ) {
52 $value = constant( strtoupper( $option ) );
53 $constant = true;
54 } else {
55 // If no constant is set, just get the value from get_site_option().
56 $value = get_site_option( $option, $default_value );
57 $constant = false;
58 }
59
60 // If $return_array, we return the $value and $constant together for easy use.
61 if ( $return_array ) {
62 return array(
63 $value,
64 $constant,
65 'value' => $value,
66 'constant' => $constant,
67 );
68 // Otherwise, just return the $value.
69 } else {
70 return $value;
71 }
72 }
73
74 /**
75 * HTTP and FastCGI friendly getenv() replacement that handles
76 * standard and REDIRECT_ environment variables, as well as HTTP
77 * headers. Users select which method to use to allow for the most
78 * secure configuration possible.
79 *
80 * @since 1.8
81 * @param string $variable Environment variable.
82 * @return string|bool
83 */
84 function shibboleth_getenv( $variable ) {
85 if ( empty( $variable ) ) {
86 return false;
87 }
88
89 // Get the specified shibboleth attribute access method; if one isn't specified
90 // simply use standard environment variables since they're the safest.
91 $method = shibboleth_getoption( 'shibboleth_attribute_access_method', 'standard' );
92 $fallback = shibboleth_getoption( 'shibboleth_attribute_access_method_fallback' );
93
94 switch ( $method ) {
95 // Use standard by default for security.
96 case 'standard':
97 default:
98 $prefix = '';
99 break;
100
101 // If specified, use redirect.
102 case 'redirect':
103 $prefix = 'REDIRECT_';
104 break;
105
106 // If specified, use http.
107 case 'http':
108 $prefix = 'HTTP_';
109 break;
110
111 // If specified, use the custom specified method.
112 case 'custom':
113 $custom = shibboleth_getoption( 'shibboleth_attribute_custom_access_method', '' );
114 $prefix = $custom;
115 break;
116 }
117
118 // Disable fallback to prevent the same variables from being checked twice.
119 if ( empty( $prefix ) ) {
120 $fallback = false;
121 }
122
123 // Using the selected attribute access method, check all possible cases.
124 $variable_under = str_replace( '-', '_', $variable );
125 $variable_upper = strtoupper( $variable );
126 $variable_under_upper = strtoupper( $variable_under );
127
128 $check_variables = array(
129 $prefix . $variable => true,
130 $prefix . $variable_under => true,
131 $prefix . $variable_upper => true,
132 $prefix . $variable_under_upper => true,
133 );
134
135 // If fallback is enabled, we will add the standard environment variables to the end of the array to allow for fallback.
136 if ( $fallback ) {
137 $check_variables += array(
138 $variable => true,
139 $variable_under => true,
140 $variable_upper => true,
141 $variable_under_upper => true,
142 );
143 }
144
145 foreach ( $check_variables as $check_variable => $true ) {
146 if ( isset( $_SERVER[ $check_variable ] ) && false !== $_SERVER[ $check_variable ] ) {
147 return sanitize_text_field( wp_unslash( $_SERVER[ $check_variable ] ) );
148 }
149 }
150
151 return false;
152 }
153
154 /**
155 * Perform automatic login. This is based on the user not being logged in,
156 * an active session and the option being set to true.
157 *
158 * @since 1.6
159 */
160 function shibboleth_auto_login() {
161 if ( ! is_user_logged_in() && shibboleth_getoption( 'shibboleth_auto_login' ) && shibboleth_session_active( true ) ) {
162 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
163 do_action( 'login_form_shibboleth' );
164
165 $userobj = wp_signon( '', true );
166 if ( ! is_wp_error( $userobj ) ) {
167 wp_safe_redirect( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '' );
168 exit();
169 }
170 }
171 }
172 add_action( 'init', 'shibboleth_auto_login' );
173
174 /**
175 * Activate the plugin. This registers default values for all of the
176 * Shibboleth options and attempts to add the appropriate mod_rewrite rules to
177 * WordPress's .htaccess file.
178 *
179 * @since 1.0
180 */
181 function shibboleth_activate_plugin() {
182 if ( version_compare( $GLOBALS['wp_version'], SHIBBOLETH_MINIMUM_WP_VERSION, '<' ) ) {
183 deactivate_plugins( plugin_basename( __FILE__ ) );
184 /* translators: 1: A version number */
185 wp_die( sprintf( esc_html( __( 'Shibboleth requires WordPress %1$s or higher!', 'shibboleth' ) ), esc_html( SHIBBOLETH_MINIMUM_WP_VERSION ) ) );
186 } elseif ( version_compare( PHP_VERSION, SHIBBOLETH_MINIMUM_PHP_VERSION, '<' ) ) {
187 deactivate_plugins( plugin_basename( __FILE__ ) );
188 /* translators: 1: A version number */
189 wp_die( sprintf( esc_html( __( 'Shibboleth requires PHP %1$s or higher!', 'shibboleth' ) ), esc_html( SHIBBOLETH_MINIMUM_PHP_VERSION ) ) );
190 }
191
192 if ( function_exists( 'switch_to_blog' ) ) {
193 if ( is_multisite() ) {
194 switch_to_blog( $GLOBALS['current_blog']->blog_id );
195 } else {
196 switch_to_blog( $GLOBALS['current_site']->blog_id );
197 }
198 }
199
200 add_site_option( 'shibboleth_login_url', get_site_option( 'home' ) . '/Shibboleth.sso/Login' );
201 add_site_option( 'shibboleth_default_to_shib_login', false );
202 add_site_option( 'shibboleth_auto_login', false );
203 add_site_option( 'shibboleth_logout_url', get_site_option( 'home' ) . '/Shibboleth.sso/Logout' );
204 add_site_option( 'shibboleth_attribute_access_method', 'standard' );
205 add_site_option( 'shibboleth_default_role', '' );
206 add_site_option( 'shibboleth_update_roles', false );
207 add_site_option( 'shibboleth_auto_combine_accounts', 'disallow' );
208 add_site_option( 'shibboleth_manually_combine_accounts', 'disallow' );
209 add_site_option( 'shibboleth_disable_local_auth', false );
210
211 $headers = array(
212 'username' => array(
213 'name' => 'eppn',
214 'managed' => 'on',
215 ),
216 'first_name' => array(
217 'name' => 'givenName',
218 'managed' => 'on',
219 ),
220 'last_name' => array(
221 'name' => 'sn',
222 'managed' => 'on',
223 ),
224 'nickname' => array(
225 'name' => 'eppn',
226 'managed' => 'off',
227 ),
228 'display_name' => array(
229 'name' => 'displayName',
230 'managed' => 'off',
231 ),
232 'email' => array(
233 'name' => 'mail',
234 'managed' => 'on',
235 ),
236 );
237 add_site_option( 'shibboleth_headers', $headers );
238
239 add_site_option( 'shibboleth_roles', array() );
240
241 shibboleth_insert_htaccess();
242
243 shibboleth_migrate_old_data();
244
245 if ( empty( shibboleth_getoption( 'shibboleth_spoof_key' ) ) ) {
246 update_site_option( 'shibboleth_spoof_key', wp_generate_password( 64, true, true ) );
247 }
248
249 update_site_option( 'shibboleth_plugin_version', SHIBBOLETH_PLUGIN_VERSION );
250
251 if ( function_exists( 'restore_current_blog' ) ) {
252 restore_current_blog();
253 }
254 }
255 register_activation_hook( __FILE__, 'shibboleth_activate_plugin' );
256
257 /**
258 * Cleanup .htaccess rules and delete the option shibboleth_plugin_version
259 * on deactivation.
260 *
261 * @since 1.0
262 */
263 function shibboleth_deactivate_plugin() {
264 shibboleth_remove_htaccess();
265 delete_site_option( 'shibboleth_plugin_version' );
266 }
267 register_deactivation_hook( __FILE__, 'shibboleth_deactivate_plugin' );
268
269
270 /**
271 * Update user meta from old IdP code to new IdP code.
272 *
273 * @since 2.5.0
274 * @param string $new_idp_code New IdP code.
275 * @param string $old_idp_code Old IdP code.
276 */
277 function shibboleth_update_idp_users( $new_idp_code, $old_idp_code ) {
278 // Update the shibboleth_account rows to have the new IdP code value.
279 $shibboleth_users = get_users(
280 array(
281 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
282 'meta_key' => 'shibboleth_account',
283 'fields' => 'ID',
284 )
285 );
286
287 foreach ( $shibboleth_users as $user_id ) {
288 update_user_meta( $user_id, 'shibboleth_account', $new_idp_code, $old_idp_code );
289 }
290 }
291
292 /**
293 * Migrate old (before version 1.9) data to a newer format that
294 * doesn't allow the default role to be stored with the rest of
295 * the role mappings.
296 */
297 function shibboleth_migrate_old_data() {
298 /**
299 * Moves data from before version 1.3 to a new header format,
300 * allowing each header to be marked as 'managed' individually
301 *
302 * @since 1.3
303 */
304 $managed = get_site_option( 'shibboleth_update_users', 'off' );
305 $headers = get_site_option( 'shibboleth_headers', array() );
306 $updated = false;
307 foreach ( $headers as $key => $value ) {
308 if ( is_string( $value ) ) {
309 $headers[ $key ] = array(
310 'name' => $value,
311 'managed' => $managed,
312 );
313 $updated = true;
314 }
315 }
316 if ( $updated ) {
317 update_site_option( 'shibboleth_headers', $headers );
318 }
319 delete_site_option( 'shibboleth_update_users' );
320
321 /**
322 * Changes to use plugin version instead of SVN revision.
323 *
324 * @since 1.8
325 */
326 delete_site_option( 'shibboleth_plugin_revision' );
327
328 /**
329 * Moves data from before version 1.9 to a new default role format,
330 * preventing a possible conflict with custom roles.
331 *
332 * @since 2.0
333 */
334 $roles = get_site_option( 'shibboleth_roles', array() );
335 if ( isset( $roles['default'] ) && '' !== $roles['default'] ) {
336 update_site_option( 'shibboleth_default_role', $roles['default'] );
337 update_site_option( 'shibboleth_create_accounts', true );
338 unset( $roles['default'] );
339 update_site_option( 'shibboleth_roles', $roles );
340 } elseif ( isset( $roles['default'] ) && '' === $roles['default'] ) {
341 update_site_option( 'shibboleth_default_role', 'subscriber' );
342 update_site_option( 'shibboleth_create_accounts', false );
343 unset( $roles['default'] );
344 update_site_option( 'shibboleth_roles', $roles );
345 }
346
347 /**
348 * Changes to support the shibboleth_getoption() function to match
349 * naming conventions of constants.
350 *
351 * @since 2.1
352 */
353 $attribute_access = get_site_option( 'shibboleth_attribute_access' );
354 if ( $attribute_access ) {
355 update_site_option( 'shibboleth_attribute_access_method', $attribute_access );
356 delete_site_option( 'shibboleth_attribute_access' );
357 }
358 $spoofkey = get_site_option( 'shibboleth_spoofkey' );
359 if ( $spoofkey ) {
360 update_site_option( 'shibboleth_spoof_key', $spoofkey );
361 delete_site_option( 'shibboleth_spoofkey' );
362 }
363 $default_login = get_site_option( 'shibboleth_default_login' );
364 if ( $default_login ) {
365 update_site_option( 'shibboleth_default_to_shib_login', $default_login );
366 delete_site_option( 'shibboleth_default_login' );
367 }
368
369 /**
370 * Convert from single to multiple IdP support.
371 *
372 * @since 2.5.0
373 */
374 $idps = get_site_option( 'shibboleth_idps', array() );
375 if ( empty( $idps ) ) {
376 $button_text = get_site_option( 'shibboleth_button_text', '' );
377 if ( 'Log in with Shibboleth' === $button_text ) {
378 $button_text = '';
379 }
380
381 $idps['preset'] = array(
382 'entity_id' => '',
383 'password_change_url' => get_site_option( 'shibboleth_password_change_url', '' ),
384 'password_reset_url' => get_site_option( 'shibboleth_password_reset_url', '' ),
385 'button_text' => $button_text,
386 );
387 update_site_option( 'shibboleth_idps', $idps );
388 delete_site_option( 'shibboleth_password_change_url' );
389 delete_site_option( 'shibboleth_password_reset_url' );
390 delete_site_option( 'shibboleth_button_text' );
391
392 // Update existing users to have the new IdP code value.
393 shibboleth_update_idp_users( 'preset', '1' );
394 }
395 }
396
397 /**
398 * Load Shibboleth admin hooks only on admin page loads.
399 *
400 * @since 1.3
401 */
402 function shibboleth_admin_hooks() {
403 if ( defined( 'WP_ADMIN' ) && WP_ADMIN === true ) {
404 require_once __DIR__ . '/options-admin.php';
405 require_once __DIR__ . '/options-user.php';
406 }
407 }
408 add_action( 'init', 'shibboleth_admin_hooks' );
409
410 /**
411 * Check if a Shibboleth session is active. If HTTP headers are being used
412 * we do additional testing to see if a spoofkey needs to be validated.
413 *
414 * @uses apply_filters calls 'shibboleth_session_active' before returning final result
415 * @param boolean $auto_login whether this is being triggered by an auto_login request or not.
416 * @return boolean|WP_Error
417 * @since 1.3
418 */
419 function shibboleth_session_active( $auto_login = false ) {
420 $active = false;
421 $method = shibboleth_getoption( 'shibboleth_attribute_access_method' );
422 $shib_headers = shibboleth_getoption( 'shibboleth_headers', array() );
423 $session = null;
424 if ( isset( $shib_headers['username']['name'] ) ) {
425 $session = shibboleth_getenv( $shib_headers['username']['name'] );
426 }
427
428 if ( $session ) {
429 if ( 'http' === $method ) {
430 /**
431 * Check spoofkey to provide some protection against HTTP header spoofing.
432 *
433 * @see https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPSpoofChecking
434 */
435 $spoofkey = shibboleth_getoption( 'shibboleth_spoof_key' );
436
437 if ( ! empty( $spoofkey ) ) {
438 $checkkey = shibboleth_getenv( 'Shib-Spoof-Check' );
439 if ( $checkkey === $spoofkey ) {
440 $active = true;
441 }
442 }
443
444 /**
445 * Deprecated: Spoof key bypass is active. This is strongly discouraged!
446 *
447 * @deprecated 2.5
448 */
449 if ( defined( 'SHIBBOLETH_BYPASS_SPOOF_CHECKING' ) && SHIBBOLETH_BYPASS_SPOOF_CHECKING ) {
450 $active = true;
451 }
452
453 if ( ! $active && ! $auto_login ) {
454 wp_die( esc_html( __( 'The Shibboleth request you submitted failed validation. Please contact your site administrator for further assistance.', 'shibboleth' ) ) );
455 }
456 } else {
457 $active = true;
458 }
459 }
460
461 $active = apply_filters( 'shibboleth_session_active', $active );
462 return $active;
463 }
464
465 /**
466 * Add an allowed_redirect_hosts filter to trust the provided URL's host.
467 *
468 * @since 2.5.3
469 * @param string $url The trusted URL.
470 */
471 function shibboleth_allow_redirect( $url ) {
472 // Note: When this plugin requires at least WordPress 4.4/4.7, we can use wp_parse_url.
473 // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
474 $allow_host = parse_url( $url, PHP_URL_HOST );
475
476 if ( empty( $allow_host ) ) {
477 return;
478 }
479
480 add_filter(
481 'allowed_redirect_hosts',
482 function ( $hosts, $host ) use ( $allow_host ) {
483 if ( ! in_array( $allow_host, $hosts, true ) ) {
484 $hosts[] = $allow_host;
485 }
486
487 return $hosts;
488 },
489 10,
490 2
491 );
492 }
493
494 /**
495 * Authenticate the user using Shibboleth. If a Shibboleth session is active,
496 * use the data provided by Shibboleth to log the user in. If a Shibboleth
497 * session is not active, redirect the user to the Shibboleth Session Initiator
498 * URL to initiate the session.
499 *
500 * @since 1.0
501 * @param null|WP_User|WP_Error $user WP_User if the user is authenticated. WP_Error or null otherwise.
502 * @param string $username Username or email address.
503 * @param string $password User password.
504 */
505 function shibboleth_authenticate( $user, $username, $password ) {
506 if ( shibboleth_session_active() ) {
507 return shibboleth_authenticate_user();
508 } else {
509 $idps = shibboleth_getoption( 'shibboleth_idps', array() );
510 $idp = key( $idps );
511 $redirect_to = null;
512
513 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
514 if ( isset( $_REQUEST['idp'] ) ) {
515 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
516 $idp = sanitize_text_field( wp_unslash( $_REQUEST['idp'] ) );
517 }
518
519 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
520 if ( isset( $_REQUEST['redirect_to'] ) ) {
521 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
522 $redirect_to = esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) );
523
524 // Make sure the redirect is in the allowed list.
525 $redirect_to = wp_validate_redirect( $redirect_to );
526 }
527
528 $initiator_url = shibboleth_session_initiator_url( $redirect_to, $idp );
529 shibboleth_allow_redirect( $initiator_url );
530 wp_safe_redirect( $initiator_url );
531 exit;
532 }
533 }
534
535 /**
536 * Add a filter for allowed_redirect_hosts so that safe redirects work on multisite.
537 *
538 * @since 2.5.3
539 * @param array $hosts An array of allowed host names.
540 * @param string $host The host name of the redirect destination; empty string if not set.
541 * @return array
542 */
543 function shibboleth_allowed_redirect_hosts( $hosts, $host ) {
544 // If the host is blank or already in the list, return early.
545 if ( empty( $host ) || in_array( $host, $hosts, true ) ) {
546 return $hosts;
547 }
548
549 // Check if this host belongs to any site in our Multisite network.
550 $site = get_site_by_path( $host, '/' );
551 if ( $site ) {
552 $hosts[] = $site->domain;
553 }
554
555 return $hosts;
556 }
557
558 /**
559 * When wp-login.php is loaded with 'action=shibboleth', hook Shibboleth
560 * into the WordPress authentication flow.
561 *
562 * @since 1.3
563 */
564 function shibboleth_login_form_shibboleth() {
565 add_filter( 'authenticate', 'shibboleth_authenticate', 10, 3 );
566
567 if ( is_multisite() ) {
568 add_filter( 'allowed_redirect_hosts', 'shibboleth_allowed_redirect_hosts', 20, 2 );
569 }
570 }
571 add_action( 'login_form_shibboleth', 'shibboleth_login_form_shibboleth' );
572
573 /**
574 * Get the associated password reset URL for the user.
575 *
576 * @since 2.5.0
577 * @param string $user_login Username.
578 * @return ?string
579 */
580 function shibboleth_get_password_reset_url( $user_login ) {
581 $user_idp = '';
582
583 $idps = shibboleth_getoption( 'shibboleth_idps', array() );
584
585 if ( ! empty( $user_login ) ) {
586 $user = get_user_by( 'login', $user_login );
587 if ( $user ) {
588 $user_idp = shibboleth_get_user_idp( $user->ID );
589
590 if ( empty( $user_idp ) ) {
591 return null;
592 }
593 }
594 } elseif ( count( $idps ) === 1 ) {
595 // If there is only one IdP, we can use it as the default.
596 $user_idp = key( $idps );
597 }
598
599 // Use the provided constant for all Shibboleth accounts.
600 if ( defined( 'SHIBBOLETH_PASSWORD_RESET_URL' ) ) {
601 return SHIBBOLETH_PASSWORD_RESET_URL;
602 }
603
604 if ( ! empty( $user_idp ) && isset( $idps[ $user_idp ] ) ) {
605 return $idps[ $user_idp ]['password_reset_url'];
606 }
607 }
608
609 /**
610 * If a Shibboleth user requests a password reset, and the Shibboleth password
611 * reset URL is set, redirect the user there.
612 *
613 * @since 1.3
614 * @param string $user_login Username.
615 */
616 function shibboleth_retrieve_password( $user_login ) {
617 $password_reset_url = shibboleth_get_password_reset_url( $user_login );
618
619 if ( ! empty( $password_reset_url ) ) {
620 shibboleth_allow_redirect( $password_reset_url );
621 wp_safe_redirect( $password_reset_url );
622 exit;
623 }
624 }
625 add_action( 'retrieve_password', 'shibboleth_retrieve_password' );
626
627
628 /**
629 * If Shibboleth is the default login method, add 'action=shibboleth' to the
630 * WordPress login URL.
631 *
632 * @since 1.0
633 * @param string $login_url The login URL.
634 */
635 function shibboleth_login_url( $login_url ) {
636 $default = shibboleth_getoption( 'shibboleth_default_to_shib_login' );
637
638 if ( $default ) {
639 $idps = shibboleth_getoption( 'shibboleth_idps', array() );
640
641 // Only send people directly to Shibboleth if there is only 1 IdP.
642 if ( count( $idps ) === 1 ) {
643 $login_url = add_query_arg( 'action', 'shibboleth', $login_url );
644 $login_url = add_query_arg( 'idp', key( $idps ), $login_url );
645 }
646 }
647
648 return $login_url;
649 }
650 add_filter( 'login_url', 'shibboleth_login_url' );
651
652
653 /**
654 * If the Shibboleth logout URL is set and the user has an active Shibboleth
655 * session, log the user out of Shibboleth after logging them out of WordPress.
656 *
657 * @since 1.0
658 */
659 function shibboleth_logout() {
660 $logout_url = shibboleth_getoption( 'shibboleth_logout_url' );
661
662 if ( ! empty( $logout_url ) && shibboleth_session_active() ) {
663 shibboleth_allow_redirect( $logout_url );
664 wp_safe_redirect( $logout_url );
665 exit;
666 }
667 }
668 add_action( 'wp_logout', 'shibboleth_logout', 20 );
669
670
671 /**
672 * Generate the URL to initiate Shibboleth login.
673 *
674 * @param string $redirect the final URL to redirect the user to after all login is complete.
675 * @param string $idp_code The chosen IdP to use for the login process.
676 * @return the URL to direct the user to in order to initiate Shibboleth login
677 * @uses apply_filters() Calls 'shibboleth_session_initiator_url' before returning session initiator URL
678 * @since 1.3
679 */
680 function shibboleth_session_initiator_url( $redirect = null, $idp_code = null ) {
681 // first build the target URL. This is the WordPress URL the user will be returned to after Shibboleth
682 // is done, and will handle actually logging the user into WordPress using the data provided by Shibboleth.
683 if ( function_exists( 'switch_to_blog' ) ) {
684 if ( ! empty( $GLOBALS['current_blog']->blog_id ) && $GLOBALS['current_blog']->blog_id !== $GLOBALS['current_site']->site_id ) {
685 switch_to_blog( $GLOBALS['current_blog']->blog_id );
686 } else {
687 switch_to_blog( $GLOBALS['current_site']->blog_id );
688 }
689 }
690
691 $target = site_url( 'wp-login.php' );
692
693 if ( function_exists( 'restore_current_blog' ) ) {
694 restore_current_blog();
695 }
696
697 $target = add_query_arg( 'action', 'shibboleth', $target );
698 if ( ! empty( $redirect ) ) {
699 $target = add_query_arg( 'redirect_to', rawurlencode( $redirect ), $target );
700 }
701
702 // now build the Shibboleth session initiator URL.
703 $initiator_url = shibboleth_getoption( 'shibboleth_login_url' );
704
705 $initiator_url = add_query_arg( 'target', rawurlencode( $target ), $initiator_url );
706
707 $idps = shibboleth_getoption( 'shibboleth_idps', array() );
708 if ( isset( $idps[ $idp_code ] ) && $idps[ $idp_code ]['entity_id'] ) {
709 $initiator_url = add_query_arg( 'entityID', rawurlencode( $idps[ $idp_code ]['entity_id'] ), $initiator_url );
710 }
711
712 $initiator_url = apply_filters( 'shibboleth_session_initiator_url', $initiator_url );
713
714 return $initiator_url;
715 }
716
717 /**
718 * Log Shibboleth message.
719 *
720 * @param string $message_type Message type.
721 * @param string $message Message.
722 * @since 2.4.3
723 */
724 function shibboleth_log_message( $message_type, $message ) {
725 static $shib_logging;
726
727 if ( ! isset( $shib_logging ) ) {
728 $shib_logging = shibboleth_getoption( 'shibboleth_logging', array() );
729 }
730
731 if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || in_array( $message_type, $shib_logging, true ) ) {
732 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
733 error_log( '[Shibboleth WordPress Plugin Logging] ' . $message );
734 }
735 }
736
737 /**
738 * Get a user's Shibboleth IdP.
739 *
740 * @param int $user_id The ID of the user.
741 * @return string
742 * @since 2.5.0
743 */
744 function shibboleth_get_user_idp( $user_id ) {
745 return get_user_meta( $user_id, 'shibboleth_account', true );
746 }
747
748 /**
749 * Set a user's Shibboleth IdP.
750 *
751 * @param int $user_id The ID of the user.
752 * @param string $user_idp IdP short label.
753 * @return bool
754 * @since 2.5.0
755 */
756 function shibboleth_set_user_idp( $user_id, $user_idp = null ) {
757 if ( empty( $user_idp ) ) {
758 $default_idp = null;
759
760 // Allow the environment variable name to be overriden.
761 $entity_id_env_var = 'Shib-Identity-Provider';
762 if ( defined( 'SHIBBOLETH_IDP_ENV_VAR' ) ) {
763 $entity_id_env_var = SHIBBOLETH_IDP_ENV_VAR;
764 }
765
766 $session_entity_id = shibboleth_getenv( $entity_id_env_var );
767
768 $idps = get_site_option( 'shibboleth_idps', array() );
769
770 foreach ( $idps as $idp_code => $idp_config ) {
771 if ( empty( $idp_config['entity_id'] ) ) {
772 $default_idp = $idp_code;
773 } elseif ( ! empty( $session_entity_id ) && $idp_config['entity_id'] === $session_entity_id ) {
774 $user_idp = $idp_code;
775 }
776 }
777
778 if ( empty( $user_idp ) ) {
779 $user_idp = $default_idp;
780 }
781 }
782
783 if ( ! empty( $user_idp ) ) {
784 update_user_meta( $user_id, 'shibboleth_account', $user_idp );
785 return true;
786 }
787
788 return false;
789 }
790
791 /**
792 * Authenticate the user based on the current Shibboleth headers.
793 *
794 * If the data available does not map to a WordPress role (based on the
795 * configured role-mapping), the user will not be allowed to login.
796 *
797 * If this is the first time we've seen this user (based on the username
798 * attribute), a new account will be created.
799 *
800 * Known users will have their profile data updated based on the Shibboleth
801 * data present if the plugin is configured to do so.
802 *
803 * @uses apply_filters() Calls 'shibboleth_override_username' before authenticating
804 * @uses apply_filters() Calls 'shibboleth_override_email' before authenticating
805 *
806 * @return WP_User|WP_Error authenticated user or error if unable to authenticate
807 * @since 1.0
808 */
809 function shibboleth_authenticate_user() {
810 $shib_headers = shibboleth_getoption( 'shibboleth_headers', array() );
811 $auto_combine_accounts = shibboleth_getoption( 'shibboleth_auto_combine_accounts' );
812 $manually_combine_accounts = shibboleth_getoption( 'shibboleth_manually_combine_accounts' );
813
814 $username = shibboleth_getenv( $shib_headers['username']['name'] );
815 $email = shibboleth_getenv( $shib_headers['email']['name'] );
816
817 /**
818 * Be VERY careful with the below two filters! They can lead to unintended
819 * consequences, such as multiple Shibboleth users mapping to the same
820 * WordPress user, or introducing security risks by improperly escaping
821 * and validating usernames and email addresses.
822 */
823
824 /**
825 * Override the username provided by Shibboleth.
826 *
827 * This can be used to escape or normalize the Shibboleth username.
828 *
829 * @param string $username
830 */
831 $username = apply_filters( 'shibboleth_override_username', $username );
832
833 /**
834 * Override the email address provided by Shibboleth.
835 *
836 * This can be used to escape or normalize the Shibboleth email address.
837 *
838 * @param string $email
839 */
840 $email = apply_filters( 'shibboleth_override_email', $email );
841
842 /**
843 * Allows a bypass mechanism for native Shibboleth authentication.
844 *
845 * Returning a non-null value from this filter will result in your value being
846 * returned to WordPress. You can prevent a user from being authenticated
847 * by returning a WP_Error object.
848 *
849 * @param null $auth
850 * @param string $username
851 */
852 $authenticate = apply_filters( 'shibboleth_authenticate_user', null, $username );
853 if ( null !== $authenticate ) {
854 return $authenticate;
855 }
856
857 // look up existing account by username, with email as a fallback.
858 $user_by = 'username';
859 $user = get_user_by( 'login', $username );
860 if ( ! $user ) {
861 $user_by = 'email';
862 $user = get_user_by( 'email', $email );
863 }
864
865 // if this account is not a Shibboleth account, then do account combine (if allowed).
866 if ( is_object( $user ) && $user->ID && ! shibboleth_get_user_idp( $user->ID ) ) {
867 $do_account_combine = false;
868 if ( 'username' === $user_by && ( 'allow' === $auto_combine_accounts || 'allow' === $manually_combine_accounts ) ) {
869 $do_account_combine = true;
870 } elseif ( 'bypass' === $auto_combine_accounts || 'bypass' === $manually_combine_accounts ) {
871 $do_account_combine = true;
872 }
873
874 if ( $do_account_combine ) {
875 if ( shibboleth_set_user_idp( $user->ID ) ) {
876 shibboleth_log_message( 'account_merge', 'SUCCESS: User ' . $user->user_login . ' (ID: ' . $user->ID . ') merged accounts automatically.' );
877 } else {
878 shibboleth_log_message( 'account_merge', 'ERROR: User ' . $user->user_login . ' (ID: ' . $user->ID . ') failed to automatically merge accounts. Reason: Unable to automatically determine IdP.' );
879 return new WP_Error( 'missing_data', __( 'Failed to match Identity Provider.', 'shibboleth' ) );
880 }
881 } elseif ( 'username' === $user_by ) {
882 shibboleth_log_message( 'account_merge', 'ERROR: User ' . $user->user_login . ' (ID: ' . $user->ID . ') failed to automatically merge accounts. Reason: An account already exists with this username.' );
883 return new WP_Error( 'invalid_username', __( 'An account already exists with this username.', 'shibboleth' ) );
884 } else {
885 shibboleth_log_message( 'account_merge', 'ERROR: User ' . $user->user_login . ' (ID: ' . $user->ID . ') failed to automatically merge accounts. Reason: An account already exists with this email.' );
886 return new WP_Error( 'invalid_email', __( 'An account already exists with this email.', 'shibboleth' ) );
887 }
888 }
889
890 // create account if new user.
891 if ( ! $user ) {
892 $user = shibboleth_create_new_user( $username, $email );
893 if ( is_wp_error( $user ) ) {
894 return new WP_Error( $user->get_error_code(), $user->get_error_message() );
895 }
896 }
897
898 if ( ! $user ) {
899 $error_message = 'Unable to create account based on data provided.';
900 shibboleth_log_message( 'account_create', 'ERROR: Unable to create account based on data provided.' );
901 return new WP_Error( 'missing_data', $error_message );
902 }
903
904 // update user data.
905 shibboleth_update_user_data( $user->ID );
906
907 $update = shibboleth_getoption( 'shibboleth_update_roles' );
908
909 if ( $update ) {
910 $user_role = shibboleth_get_user_role();
911 $user->set_role( $user_role );
912 shibboleth_log_message( 'role_update', 'SUCCESS: User ' . $user->user_login . ' (ID: ' . $user->ID . ') role was updated to ' . $user_role . '.' );
913 do_action( 'shibboleth_set_user_roles', $user );
914 }
915
916 shibboleth_log_message( 'auth', 'SUCCESS: User ' . $user->user_login . ' (ID: ' . $user->ID . ') successfully authenticated.' );
917 return $user;
918 }
919
920
921 /**
922 * Create a new WordPress user account, and mark it as a Shibboleth account.
923 *
924 * @param string $user_login login name for the new user.
925 * @param string $user_email email address for the new user.
926 * @return object WP_User object for newly created user.
927 * @since 1.0
928 */
929 function shibboleth_create_new_user( $user_login, $user_email ) {
930 $create_accounts = shibboleth_getoption( 'shibboleth_create_accounts' );
931 $user_role = shibboleth_get_user_role();
932
933 if ( ! empty( $create_accounts ) ) {
934 if ( empty( $user_login ) || empty( $user_email ) || '_no_account' === $user_role ) {
935 return null;
936 }
937
938 // create account and flag as a shibboleth account.
939 $user_id = wp_insert_user(
940 array(
941 'user_login' => $user_login,
942 'user_email' => $user_email,
943 'user_pass' => null,
944 )
945 );
946 if ( is_wp_error( $user_id ) ) {
947 shibboleth_log_message( 'account_create', 'ERROR: Unable to create account based on data provided. Reason: ' . $user_id->get_error_message() . '.' );
948 return new WP_Error( 'account_create_failed', $user_id->get_error_message() );
949 } else {
950 $user = new WP_User( $user_id );
951 shibboleth_set_user_idp( $user->ID );
952
953 wp_new_user_notification( $user_id );
954
955 // always update user data and role on account creation.
956 shibboleth_update_user_data( $user->ID, true );
957 $user->set_role( $user_role );
958 do_action( 'shibboleth_set_user_roles', $user );
959 shibboleth_log_message( 'account_create', 'SUCCESS: User ' . $user->user_login . ' (ID: ' . $user->ID . ') was created with role ' . ( $user_role ? $user_role : 'none' ) . '.' );
960 return $user;
961 }
962 } else {
963 shibboleth_log_message( 'auth', 'ERROR: User account does not exist and account creation is disabled.' );
964 return new WP_Error( 'no_access', __( 'You do not have sufficient access.', 'shibboleth' ) );
965 }
966 }
967
968 /**
969 * Get the role the current user should have. This is determined by the role
970 * mapping configured for the plugin, and the Shibboleth headers present at the
971 * time of login.
972 *
973 * @return string the role the current user should have
974 * @uses apply_filters() Calls 'shibboleth_roles' after retrieving shibboleth_roles array
975 * @uses apply_filters() Calls 'shibboleth_user_role' before returning final user role
976 * @since 1.0
977 */
978 function shibboleth_get_user_role() {
979 $shib_roles = apply_filters( 'shibboleth_roles', shibboleth_getoption( 'shibboleth_roles', array() ) );
980 $user_role = shibboleth_getoption( 'shibboleth_default_role' );
981
982 $roles = wp_roles();
983
984 foreach ( $roles->role_names as $key => $name ) {
985 if ( empty( $shib_roles[ $key ]['header'] ) || empty( $shib_roles[ $key ]['value'] ) ) {
986 continue;
987 }
988
989 $role_header = $shib_roles[ $key ]['header'];
990 $role_value = $shib_roles[ $key ]['value'];
991 $values = explode( ';', shibboleth_getenv( $role_header ) );
992 if ( in_array( $role_value, $values, true ) ) {
993 $user_role = $key;
994 break;
995 }
996 }
997
998 $user_role = apply_filters( 'shibboleth_user_role', $user_role );
999
1000 return $user_role;
1001 }
1002
1003
1004 /**
1005 * Get the user fields that are managed by Shibboleth.
1006 *
1007 * @return Array user fields managed by Shibboleth
1008 * @since 1.3
1009 */
1010 function shibboleth_get_managed_user_fields() {
1011 $shib_headers = shibboleth_getoption( 'shibboleth_headers', array() );
1012
1013 $managed = array();
1014
1015 foreach ( $shib_headers as $name => $value ) {
1016 if ( isset( $value['managed'] ) ) {
1017 if ( $value['managed'] ) {
1018 $managed[] = $name;
1019 }
1020 }
1021 }
1022
1023 return $managed;
1024 }
1025
1026
1027 /**
1028 * Update the user data for the specified user based on the current Shibboleth headers. Unless
1029 * the 'force_update' parameter is true, only the user fields marked as 'managed' fields will be
1030 * updated.
1031 *
1032 * @param int $user_id ID of the user to update.
1033 * @param boolean $force_update force update of user data, regardless of 'managed' flag on fields.
1034 * @uses apply_filters() Calls 'shibboleth_user_*' before setting user attributes,
1035 * where '*' is one of: login, nicename, first_name, last_name,
1036 * nickname, display_name, email
1037 * @since 1.0
1038 */
1039 function shibboleth_update_user_data( $user_id, $force_update = false ) {
1040 $shib_headers = shibboleth_getoption( 'shibboleth_headers', array() );
1041
1042 $user_fields = array(
1043 'user_login' => 'username',
1044 'user_nicename' => 'username',
1045 'first_name' => 'first_name',
1046 'last_name' => 'last_name',
1047 'nickname' => 'nickname',
1048 'display_name' => 'display_name',
1049 'user_email' => 'email',
1050 );
1051
1052 $user_data = array(
1053 'ID' => $user_id,
1054 );
1055
1056 foreach ( $user_fields as $field => $header ) {
1057 $managed = false;
1058 if ( isset( $shib_headers[ $header ]['managed'] ) ) {
1059 $managed = $shib_headers[ $header ]['managed'];
1060 }
1061 if ( $force_update || $managed ) {
1062 $filter = ( strpos( $field, 'user_' ) === 0 ? '' : 'user_' ) . $field;
1063 $user_data[ $field ] = apply_filters( 'shibboleth_' . $filter, shibboleth_getenv( $shib_headers[ $header ]['name'] ) );
1064 }
1065 }
1066
1067 // Shibboleth users do not use their email address for authentication.
1068 add_filter( 'send_email_change_email', '__return_false' );
1069
1070 wp_update_user( $user_data );
1071 }
1072
1073
1074 /**
1075 * Sanitize the nicename using sanitize_title
1076 *
1077 * @since 1.4
1078 * @see http://wordpress.org/support/topic/377030
1079 */
1080 add_filter( 'shibboleth_user_nicename', 'sanitize_title' );
1081
1082 /**
1083 * Enqueue styles for the Shibboleth WordPress admin pages.
1084 *
1085 * @param string $hook_suffix The current admin page.
1086 */
1087 function shibboleth_admin_enqueue_scripts( $hook_suffix ) {
1088 if ( 'settings_page_shibboleth-options' !== $hook_suffix ) {
1089 return;
1090 }
1091
1092 wp_enqueue_style( 'shibboleth-options', plugins_url( 'assets/css/shibboleth-options.css', __FILE__ ), array(), SHIBBOLETH_PLUGIN_VERSION );
1093 }
1094 add_action( 'admin_enqueue_scripts', 'shibboleth_admin_enqueue_scripts' );
1095
1096 /**
1097 * Enqueues scripts and styles necessary for the Shibboleth button.
1098 *
1099 * @since 2.0
1100 */
1101 function shibboleth_login_enqueue_scripts() {
1102 global $action;
1103
1104 // Only add scripts for the login action to avoid breaking other forms.
1105 if ( 'login' === $action || 'shibboleth' === $action ) {
1106 wp_enqueue_style( 'shibboleth-login', plugins_url( 'assets/css/shibboleth_login_form.css', __FILE__ ), array( 'login' ), SHIBBOLETH_PLUGIN_VERSION );
1107 wp_enqueue_script( 'shibboleth-login', plugins_url( 'assets/js/shibboleth_login_form.js', __FILE__ ), array( 'jquery' ), SHIBBOLETH_PLUGIN_VERSION, true );
1108 }
1109 }
1110 add_action( 'login_enqueue_scripts', 'shibboleth_login_enqueue_scripts' );
1111
1112 /**
1113 * Prevents local WordPress authentication if disabled by an administrator.
1114 *
1115 * @since 2.0
1116 */
1117 function shibboleth_disable_login() {
1118 $disable = shibboleth_getoption( 'shibboleth_disable_local_auth', false );
1119
1120 $bypass = defined( 'SHIBBOLETH_ALLOW_LOCAL_AUTH' ) && SHIBBOLETH_ALLOW_LOCAL_AUTH;
1121
1122 if ( $disable && ! $bypass ) {
1123 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1124 if ( isset( $_GET['action'] ) && 'lostpassword' === $_GET['action'] ) {
1125 // Disable the ability to reset passwords from wp-login.php.
1126 add_filter( 'allow_password_reset', '__return_false' );
1127 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1128 } elseif ( isset( $_POST['log'] ) || isset( $_POST['user_login'] ) ) {
1129 // Disable the ability to login using local authentication.
1130 wp_die( esc_html( __( 'Shibboleth authentication is required.', 'shibboleth' ) ) );
1131 }
1132 }
1133 }
1134 add_action( 'login_init', 'shibboleth_disable_login' );
1135
1136 /**
1137 * Disables wp-login.php login form if disabled by an administrator.
1138 *
1139 * @since 2.0
1140 */
1141 function shibboleth_disable_login_form() {
1142 $disable = shibboleth_getoption( 'shibboleth_disable_local_auth', false );
1143 $bypass = defined( 'SHIBBOLETH_ALLOW_LOCAL_AUTH' ) && SHIBBOLETH_ALLOW_LOCAL_AUTH;
1144
1145 if ( $disable && ! $bypass ) {
1146 $password_reset_url = shibboleth_get_password_reset_url( '' );
1147 ?>
1148 <style type="text/css">
1149 .login #loginform p,
1150 .login #loginform .user-pass-wrap {
1151 display: none;
1152 }
1153 <?php if ( ! $password_reset_url ) { ?>
1154 .login #nav {
1155 display: none;
1156 }
1157 <?php } ?>
1158 </style>
1159 <?php
1160 }
1161 }
1162 add_action( 'login_enqueue_scripts', 'shibboleth_disable_login_form' );
1163
1164 /**
1165 * Updates the lost password URL, if specified.
1166 *
1167 * @param string $url original password reset URL.
1168 * @since 2.1
1169 */
1170 function shibboleth_custom_password_reset_url( $url ) {
1171 $password_reset_url = shibboleth_get_password_reset_url( '' );
1172
1173 if ( $password_reset_url ) {
1174 return $password_reset_url;
1175 } else {
1176 return $url;
1177 }
1178 }
1179 add_filter( 'lostpassword_url', 'shibboleth_custom_password_reset_url' );
1180
1181 /**
1182 * Add a "Log in with Shibboleth" link to the WordPress login form. This link
1183 * will be wrapped in a <p> with an id value of "shibboleth_login" so that
1184 * deployers can style this however they choose.
1185 *
1186 * @since 1.0
1187 */
1188 function shibboleth_login_form() {
1189 global $wp;
1190
1191 $idps = shibboleth_getoption( 'shibboleth_idps', array() );
1192 if ( empty( $idps ) ) {
1193 return;
1194 }
1195
1196 $url = false;
1197 if ( ! empty( $wp->request ) ) {
1198 $url = wp_login_url( home_url( $wp->request ) );
1199 }
1200 $login_url = add_query_arg( 'action', 'shibboleth', $url );
1201 $login_url = remove_query_arg( 'reauth', $login_url );
1202 $disable = shibboleth_getoption( 'shibboleth_disable_local_auth', false );
1203
1204 $first = true;
1205
1206 foreach ( $idps as $idp_code => $idp ) {
1207 $idp_login_url = add_query_arg( 'idp', $idp_code, $login_url );
1208
1209 if ( defined( 'SHIBBOLETH_BUTTON_TEXT' ) && SHIBBOLETH_BUTTON_TEXT ) {
1210 $button_text = SHIBBOLETH_BUTTON_TEXT;
1211 } elseif ( ! empty( $idp['button_text'] ) ) {
1212 $button_text = $idp['button_text'];
1213 } else {
1214 $button_text = __( 'Log in with Shibboleth', 'shibboleth' );
1215 }
1216 ?>
1217 <div class="shibboleth-wrap" <?php echo ( $first && $disable ) ? 'style="margin-top:0;"' : ''; ?>>
1218 <?php
1219 if ( $first && ! $disable ) {
1220 ?>
1221 <div class="shibboleth-or">
1222 <span><?php esc_html_e( 'Or', 'shibboleth' ); ?></span>
1223 </div>
1224 <?php
1225 }
1226 ?>
1227 <a href="<?php echo esc_url( $idp_login_url ); ?>" rel="nofollow" class="shibboleth-button button button-primary default">
1228 <span class="shibboleth-icon"></span>
1229 <?php echo esc_html( $button_text ); ?>
1230 </a>
1231 </div>
1232 <?php
1233 $first = false;
1234 }
1235 }
1236 add_action( 'login_form', 'shibboleth_login_form' );
1237
1238
1239 /**
1240 * Insert directives into .htaccess file to enable Shibboleth Lazy Sessions.
1241 *
1242 * @since 1.0
1243 */
1244 function shibboleth_insert_htaccess() {
1245 $disabled = defined( 'SHIBBOLETH_DISALLOW_FILE_MODS' ) && SHIBBOLETH_DISALLOW_FILE_MODS;
1246
1247 if ( got_mod_rewrite() && ! $disabled ) {
1248 $htaccess = get_home_path() . '.htaccess';
1249 $rules = array( '<IfModule mod_shib>', 'AuthType shibboleth', 'Require shibboleth', '</IfModule>', '<IfModule mod_shib.c>', 'AuthType shibboleth', 'Require shibboleth', '</IfModule>', '<IfModule mod_shib.cpp>', 'AuthType shibboleth', 'Require shibboleth', '</IfModule>' );
1250 insert_with_markers( $htaccess, 'Shibboleth', $rules );
1251 }
1252 }
1253
1254
1255 /**
1256 * Remove directives from .htaccess file to enable Shibboleth Lazy Sessions.
1257 *
1258 * @since 1.1
1259 */
1260 function shibboleth_remove_htaccess() {
1261 $disabled = defined( 'SHIBBOLETH_DISALLOW_FILE_MODS' ) && SHIBBOLETH_DISALLOW_FILE_MODS;
1262
1263 if ( got_mod_rewrite() && ! $disabled ) {
1264 $htaccess = get_home_path() . '.htaccess';
1265 insert_with_markers( $htaccess, 'Shibboleth', array() );
1266 }
1267 }
1268
1269 /**
1270 * Load localization files.
1271 *
1272 * @since 1.7
1273 */
1274 function shibboleth_load_textdomain() {
1275 load_plugin_textdomain( 'shibboleth', false, dirname( plugin_basename( __FILE__ ) ) . '/localization/' );
1276 }
1277 add_action( 'plugins_loaded', 'shibboleth_load_textdomain' );
1278