PluginProbe
Authorizer / 3.9.1
Authorizer v3.9.1
3.15.3 3.15.2 3.15.1 3.15.0 3.14.3 3.14.4 3.14.2 3.14.1 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.9.0 2.9.1 2.9.10 2.9.11 2.9.12 2.9.13 2.9.2 2.9.3 2.9.6 All 126 releases
← All changes | src/authorizer/class-authorization.php +208 -68 2.9.03.9.1 View file →
@@ -14,9 +14,9 @@
14 14
15 15 /**
16 16 * Implements the authorization (roles and permissions) features of the plugin.
17 17 */
18 -class Authorization extends Static_Instance {
18 +class Authorization extends Singleton {
19 19
20 20 /**
21 21 * This function will fail with a wp_die() message to the user if they
22 22 * don't have access.
@@ -23,9 +23,10 @@
23 23 *
24 24 * @param WP_User $user User to check.
25 25 * @param array $user_emails Array of user's plaintext emails (in case current user doesn't have a WP account).
26 26 * @param array $user_data Array of keys for email, username, first_name, last_name,
27 - * authenticated_by, google_attributes, cas_attributes, ldap_attributes.
27 + * authenticated_by, google_attributes, cas_attributes, ldap_attributes,
28 + * oauth2_attributes.
28 29 * @return WP_Error|WP_User
29 30 * WP_Error if there was an error on user creation / adding user to blog.
30 31 * WP_Error / wp_die() if user does not have access.
31 32 * WP_User if user has access.
@@ -45,8 +46,40 @@
45 46 $auth_settings_access_users_approved_multi
46 47 )
47 48 );
48 49
50 + // Detect whether this user's first and last name should be updated below
51 + // (if the external CAS/LDAP service provides a different value, the option
52 + // is set to update it, and it's empty if the option to only set it if empty
53 + // is enabled).
54 + $should_update_first_name =
55 + $user && ! empty( $user_data['first_name'] ) && $user_data['first_name'] !== $user->first_name &&
56 + (
57 + (
58 + ! empty( $user_data['authenticated_by'] ) && 'cas' === $user_data['authenticated_by'] &&
59 + ! empty( $auth_settings['cas_attr_update_on_login'] ) &&
60 + ( '1' === $auth_settings['cas_attr_update_on_login'] || ( 'update-if-empty' === $auth_settings['cas_attr_update_on_login'] && empty( $user->first_name ) ) )
61 + ) || (
62 + ! empty( $user_data['authenticated_by'] ) && 'ldap' === $user_data['authenticated_by'] &&
63 + ! empty( $auth_settings['ldap_attr_update_on_login'] ) &&
64 + ( '1' === $auth_settings['ldap_attr_update_on_login'] || ( 'update-if-empty' === $auth_settings['ldap_attr_update_on_login'] && empty( $user->first_name ) ) )
65 + )
66 + );
67 +
68 + $should_update_last_name =
69 + $user && ! empty( $user_data['last_name'] ) && $user_data['last_name'] !== $user->last_name &&
70 + (
71 + (
72 + ! empty( $user_data['authenticated_by'] ) && 'cas' === $user_data['authenticated_by'] &&
73 + ! empty( $auth_settings['cas_attr_update_on_login'] ) &&
74 + ( '1' === $auth_settings['cas_attr_update_on_login'] || ( 'update-if-empty' === $auth_settings['cas_attr_update_on_login'] && empty( $user->last_name ) ) )
75 + ) || (
76 + ! empty( $user_data['authenticated_by'] ) && 'ldap' === $user_data['authenticated_by'] &&
77 + ! empty( $auth_settings['ldap_attr_update_on_login'] ) &&
78 + ( '1' === $auth_settings['ldap_attr_update_on_login'] || ( 'update-if-empty' === $auth_settings['ldap_attr_update_on_login'] && empty( $user->last_name ) ) )
79 + )
80 + );
81 +
49 82 /**
50 83 * Filter whether to block the currently logging in user based on any of
51 84 * their user attributes.
52 85 *
@@ -70,9 +103,9 @@
70 103 array_push(
71 104 $auth_settings_access_users_blocked,
72 105 array(
73 106 'email' => Helper::lowercase( $user_email ),
74 - 'date_added' => date( 'M Y' ),
107 + 'date_added' => wp_date( 'M Y' ),
75 108 )
76 109 );
77 110 update_option( 'auth_settings_access_users_blocked', $auth_settings_access_users_blocked );
78 111 }
@@ -127,8 +160,22 @@
127 160 * @param array $user_data User data returned from external service.
128 161 */
129 162 $automatically_approve_login = apply_filters( 'authorizer_automatically_approve_login', false, $user_data );
130 163
164 + // If this externally-authenticated user is an existing administrator (admin
165 + // in single site mode, or super admin in network mode), and isn't blocked,
166 + // let them in. Update their first/last name if needed (CAS/LDAP).
167 + if ( $user && is_super_admin( $user->ID ) ) {
168 + if ( $should_update_first_name ) {
169 + update_user_meta( $user->ID, 'first_name', $user_data['first_name'] );
170 + }
171 + if ( $should_update_last_name ) {
172 + update_user_meta( $user->ID, 'last_name', $user_data['last_name'] );
173 + }
174 +
175 + return $user;
176 + }
177 +
131 178 // Iterate through each of the email addresses provided by the external
132 179 // service and determine if any of them have access.
133 180 $last_email = end( $user_emails );
134 181 reset( $user_emails );
@@ -134,15 +181,8 @@
134 181 reset( $user_emails );
135 182 foreach ( $user_emails as $user_email ) {
136 183 $is_newly_approved_user = false;
137 184
138 - // If this externally authenticated user is an existing administrator
139 - // (administrator in single site mode, or super admin in network mode),
140 - // and is not in the blocked list, let them in.
141 - if ( $user && is_super_admin( $user->ID ) ) {
142 - return $user;
143 - }
144 -
145 185 // If this externally authenticated user isn't in the approved list
146 186 // and login access is set to "All authenticated users," or if they were
147 187 // automatically approved in the "authorizer_approve_login" filter
148 188 // above, then add them to the approved list (they'll get an account
@@ -168,9 +208,9 @@
168 208 // Add this user to the approved list.
169 209 $approved_user = array(
170 210 'email' => Helper::lowercase( $user_email ),
171 211 'role' => $approved_role,
172 - 'date_added' => date( 'Y-m-d H:i:s' ),
212 + 'date_added' => wp_date( 'Y-m-d H:i:s' ),
173 213 );
174 214 array_push( $auth_settings_access_users_approved, $approved_user );
175 215 array_push( $auth_settings_access_users_approved_single, $approved_user );
176 216 update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved_single );
@@ -181,13 +221,37 @@
181 221 // if necessary).
182 222 if ( $is_newly_approved_user || $this->is_email_in_list( $user_email, 'approved' ) ) {
183 223 $user_info = $is_newly_approved_user ? $approved_user : Helper::get_user_info_from_list( $user_email, $auth_settings_access_users_approved );
184 224
185 - // If this user's role was modified above (in the
186 - // authorizer_custom_role filter), use that value instead of
187 - // whatever is specified in the approved list.
188 - if ( $default_role !== $approved_role ) {
225 + // If this user's role was modified above (in the authorizer_custom_role
226 + // filter), update the role in the approved list and use that role
227 + // (i.e., if the roles are out of sync, use the authorizer_custom_role
228 + // value instead of the role in the approved list).
229 + if ( has_filter( 'authorizer_custom_role' ) ) {
189 230 $user_info['role'] = $approved_role;
231 +
232 + // Find the user in either the single site or multisite approved list
233 + // and update their role there if different.
234 + foreach ( $auth_settings_access_users_approved_single as $index => $auth_settings_access_user_approved_single ) {
235 + if ( $user_info['email'] === $auth_settings_access_user_approved_single['email'] ) {
236 + if ( $auth_settings_access_users_approved_single[ $index ]['role'] !== $approved_role ) {
237 + $auth_settings_access_users_approved_single[ $index ]['role'] = $approved_role;
238 + update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved_single );
239 + }
240 + break;
241 + }
242 + }
243 + if ( is_multisite() ) {
244 + foreach ( $auth_settings_access_users_approved_multi as $index => $auth_settings_access_user_approved_multi ) {
245 + if ( $user_info['email'] === $auth_settings_access_user_approved_multi['email'] ) {
246 + if ( $auth_settings_access_users_approved_multi[ $index ]['role'] !== $approved_role ) {
247 + $auth_settings_access_users_approved_multi[ $index ]['role'] = $approved_role;
248 + update_blog_option( get_main_site_id( get_main_network_id() ), 'auth_multisite_settings_access_users_approved', $auth_settings_access_users_approved_multi );
249 + }
250 + break;
251 + }
252 + }
253 + }
190 254 }
191 255
192 256 // If the approved external user does not have a WordPress account, create it.
193 257 if ( ! $user ) {
@@ -210,9 +274,9 @@
210 274 'user_pass' => wp_generate_password(), // random password.
211 275 'first_name' => array_key_exists( 'first_name', $user_data ) ? $user_data['first_name'] : '',
212 276 'last_name' => array_key_exists( 'last_name', $user_data ) ? $user_data['last_name'] : '',
213 277 'user_email' => Helper::lowercase( $user_info['email'] ),
214 - 'user_registered' => date( 'Y-m-d H:i:s' ),
278 + 'user_registered' => wp_date( 'Y-m-d H:i:s' ),
215 279 'role' => $user_info['role'],
216 280 )
217 281 );
218 282
@@ -320,43 +384,14 @@
320 384 }
321 385 }
322 386 }
323 387 } else {
324 - // Update first/last names of WordPress user from external
325 - // service if that option is set.
326 - if ( ( array_key_exists( 'authenticated_by', $user_data ) && 'cas' === $user_data['authenticated_by'] && array_key_exists( 'cas_attr_update_on_login', $auth_settings ) && 1 === intval( $auth_settings['cas_attr_update_on_login'] ) ) || ( array_key_exists( 'authenticated_by', $user_data ) && 'ldap' === $user_data['authenticated_by'] && array_key_exists( 'ldap_attr_update_on_login', $auth_settings ) && 1 === intval( $auth_settings['ldap_attr_update_on_login'] ) ) ) {
327 - if ( array_key_exists( 'first_name', $user_data ) && 0 < strlen( $user_data['first_name'] ) ) {
328 - wp_update_user(
329 - array(
330 - 'ID' => $user->ID,
331 - 'first_name' => $user_data['first_name'],
332 - )
333 - );
334 - }
335 - if ( array_key_exists( 'last_name', $user_data ) && strlen( $user_data['last_name'] ) > 0 ) {
336 - wp_update_user(
337 - array(
338 - 'ID' => $user->ID,
339 - 'last_name' => $user_data['last_name'],
340 - )
341 - );
342 - }
388 + // Update first/last name from CAS/LDAP if needed.
389 + if ( $should_update_first_name ) {
390 + update_user_meta( $user->ID, 'first_name', $user_data['first_name'] );
343 391 }
344 -
345 - // Update this user's role if it was modified in the
346 - // authorizer_custom_role filter.
347 - if ( $default_role !== $approved_role ) {
348 - // Update user's role in WordPress.
349 - $user->set_role( $approved_role );
350 -
351 - // Update user's role in this site's approved list and save.
352 - foreach ( $auth_settings_access_users_approved_single as $key => $existing_user ) {
353 - if ( 0 === strcasecmp( $user->user_email, $existing_user['email'] ) ) {
354 - $auth_settings_access_users_approved_single[ $key ]['role'] = $approved_role;
355 - break;
356 - }
357 - }
358 - update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved_single );
392 + if ( $should_update_last_name ) {
393 + update_user_meta( $user->ID, 'last_name', $user_data['last_name'] );
359 394 }
360 395 }
361 396
362 397 // If this is multisite, add new user to current blog.
@@ -420,8 +455,15 @@
420 455 }
421 456 }
422 457 }
423 458
459 + // Fetch the external service this user authenticated with, and append
460 + // it to the logout URL below (so we can fire custom logout routines in
461 + // custom_logout() based on their external service. This is necessary
462 + // because a pending user does not have a WP_User, and thus no
463 + // "authenticated_by" usermeta that is normally used to do this.
464 + $external_param = isset( $user_data['authenticated_by'] ) ? '&external=' . $user_data['authenticated_by'] : '';
465 +
424 466 // Notify user about pending status and return without authenticating them.
425 467 // phpcs:ignore WordPress.Security.NonceVerification
426 468 $redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : home_url();
427 469 $page_title = get_bloginfo( 'name' ) . ' - Access Pending';
@@ -428,9 +470,9 @@
428 470 $error_message =
429 471 apply_filters( 'the_content', $auth_settings['access_pending_redirect_to_message'] ) .
430 472 '<hr />' .
431 473 '<p style="text-align: center;">' .
432 - '<a class="button" href="' . wp_logout_url( $redirect_to ) . '">' .
474 + '<a class="button" href="' . wp_logout_url( $redirect_to ) . $external_param . '">' .
433 475 __( 'Back', 'authorizer' ) .
434 476 '</a></p>';
435 477 update_option( 'auth_settings_advanced_login_error', $error_message );
436 478 wp_die( wp_kses( $error_message, Helper::$allowed_html ), esc_html( $page_title ) );
@@ -438,9 +480,8 @@
438 480 }
439 481
440 482 // Sanity check: if we made it here without returning, something has gone wrong.
441 483 return new \WP_Error( 'invalid_login', __( 'Invalid login attempted.', 'authorizer' ) );
442 -
443 484 }
444 485
445 486
446 487 /**
@@ -447,9 +488,9 @@
447 488 * Restrict access to WordPress site based on settings (everyone, logged_in_users).
448 489 *
449 490 * Action: parse_request
450 491 *
451 - * @param array $wp WordPress object.
492 + * @param WP $wp WordPress object.
452 493 * @return WP|void WP object when passing through to WordPress authentication, or void.
453 494 */
454 495 public function restrict_access( $wp ) {
455 496 // Grab plugin settings.
@@ -468,15 +509,11 @@
468 509 // Allow access if option is set to 'everyone'.
469 510 ( 'everyone' === $auth_settings['access_who_can_view'] ) ||
470 511 // Allow access to approved external users and logged in users if option is set to 'logged_in_users'.
471 512 ( 'logged_in_users' === $auth_settings['access_who_can_view'] && Helper::is_user_logged_in_and_blog_user() && $this->is_email_in_list( $current_user->user_email, 'approved' ) ) ||
472 - // Allow access for requests to /wp-json/oauth1 so oauth clients can authenticate to use the REST API.
473 - ( property_exists( $wp, 'matched_query' ) && stripos( $wp->matched_query, 'rest_oauth1=' ) === 0 ) ||
474 - // Allow access for non-GET requests to /wp-json/*, since REST API authentication already covers them.
475 - ( property_exists( $wp, 'matched_query' ) && 0 === stripos( $wp->matched_query, 'rest_route=' ) && isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] ) ||
476 - // Allow access for GET requests to /wp-json/ (root), since REST API discovery calls rely on this.
477 - ( property_exists( $wp, 'matched_query' ) && 'rest_route=/' === $wp->matched_query )
478 - // Note that GET requests to a rest endpoint will be restricted by authorizer. In that case, error messages will be returned as JSON.
513 + // Allow REST API requests (access is determined later in the rest_authentication_errors hook).
514 + // See: https://github.com/WordPress/WordPress/blob/8e41746cb11271d063608a63e3f6091a8685e677/wp-includes/rest-api.php#L131-L133.
515 + ( ! empty( $GLOBALS['wp']->query_vars['rest_route'] ) )
479 516 );
480 517
481 518 /**
482 519 * Developers can use the `authorizer_has_access` filter to override
@@ -505,9 +542,9 @@
505 542 return $wp;
506 543 }
507 544
508 545 // Allow HEAD requests to the root (usually discovery from a REST client).
509 - if ( 'HEAD' === $_SERVER['REQUEST_METHOD'] && empty( $wp->request ) && empty( $wp->matched_query ) ) {
546 + if ( ! empty( $_SERVER['REQUEST_METHOD'] ) && 'HEAD' === $_SERVER['REQUEST_METHOD'] && empty( $wp->request ) && empty( $wp->matched_query ) ) {
510 547 return $wp;
511 548 }
512 549
513 550 /* We've determined that the current user doesn't have access, so we deal with them now. */
@@ -568,9 +605,10 @@
568 605
569 606 // Check to see if the requested category is public. If so, show it.
570 607 $current_category_name = property_exists( $wp, 'query_vars' ) && array_key_exists( 'category_name', $wp->query_vars ) && strlen( $wp->query_vars['category_name'] ) > 0 ? $wp->query_vars['category_name'] : '';
571 608 if ( $current_category_name ) {
572 - $current_category_name = end( explode( '/', $current_category_name ) );
609 + $current_category_name_pieces = explode( '/', $current_category_name );
610 + $current_category_name = end( $current_category_name_pieces );
573 611 if ( in_array( 'cat_' . $current_category_name, $auth_settings['access_public_pages'], true ) ) {
574 612 if ( 'no_warning' === $auth_settings['access_public_warning'] ) {
575 613 update_option( 'auth_settings_advanced_public_notice', false );
576 614 } else {
@@ -616,20 +654,123 @@
616 654 // Sanity check: we should never get here.
617 655 wp_die( '<p>Access denied.</p>', 'Site Access Restricted' );
618 656 }
619 657
658 + /**
659 + * If we're showing search results or a post listing (home or archive page) to
660 + * an anonymous user, and Authorizer is configured to only allow logged in
661 + * users to see the site, filter the query to only posts marked public.
662 + *
663 + * Action: pre_get_posts
664 + *
665 + * @param WP_Query $query The WP_Query instance (passed by reference).
666 + * @return void
667 + */
668 + public function remove_private_pages_from_search_and_archives( $query ) {
669 + // It's possible for pre_get_posts to fire before wp-includes/pluggable.php
670 + // is loaded, so verify before using the is_user_logged_in() function.
671 + if ( ! function_exists( 'is_user_logged_in' ) ) {
672 + require ABSPATH . WPINC . '/pluggable.php';
673 + }
620 674
675 + // Fix for edge case when viewing admin pages in Pressbooks (Undefined
676 + // constant "SECURE_AUTH_COOKIE").
677 + if ( ! defined( 'SECURE_AUTH_COOKIE' ) ) {
678 + wp_cookie_constants();
679 + }
680 +
681 + // Do nothing if user is logged in, this isn't the main query, or we're not
682 + // showing search results, home page, or an archive page.
683 + if (
684 + is_user_logged_in() || ! $query->is_main_query() ||
685 + ! ( $query->is_search() || $query->is_home() || $query->is_archive() )
686 + ) {
687 + return;
688 + }
689 +
690 + $options = Options::get_instance();
691 + $who_can_view = $options->get( 'access_who_can_view' );
692 + $public_pages = $options->get( 'access_public_pages' );
693 + $public_pages = is_array( $public_pages ) ? $public_pages : array();
694 +
695 + // Do nothing if this site isn't restricted to logged in users only.
696 + if ( 'logged_in_users' !== $who_can_view ) {
697 + return;
698 + }
699 +
700 + // Check for special public types (home, 404, categories).
701 + $public_category_ids = array();
702 + foreach ( $public_pages as $index => $public_page ) {
703 + if ( 'home' === $public_page || 'auth_public_404' === $public_page ) {
704 + unset( $public_pages[ $index ] );
705 + } elseif ( 'cat_' === substr( $public_page, 0, 4 ) ) {
706 + $public_category_name = substr( $public_page, 4 );
707 + unset( $public_pages[ $index ] );
708 + $public_category_ids[] = get_cat_ID( $public_category_name );
709 + }
710 + }
711 + if ( ! empty( $public_category_ids ) ) {
712 + $pages_in_public_categories = get_posts( array(
713 + 'posts_per_page' => -1,
714 + 'fields' => 'ids',
715 + 'category__in' => $public_category_ids,
716 + ) );
717 + $public_pages = array_merge( $public_pages, $pages_in_public_categories );
718 + }
719 +
720 + $query->set( 'post__in', $public_pages );
721 + }
722 +
621 723 /**
724 + * Prevent REST API access if user isn't authenticated and "only logged in
725 + * users can see the site" is enabled.
726 + *
727 + * Filter: rest_authentication_errors
728 + *
729 + * @param WP_Error|null|true $errors WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded.
730 + * @return WP_Error|null|true WP_Error if not logged in and "only logged in users can see the site" is enabled.
731 + */
732 + public function restrict_rest_api( $errors ) {
733 + // If there is already an error, just return that.
734 + if ( ! empty( $errors ) ) {
735 + return $errors;
736 + }
737 +
738 + // If user isn't logged in, check for "only logged in users can see the site".
739 + if ( ! is_user_logged_in() ) {
740 + // Grab plugin settings.
741 + $options = Options::get_instance();
742 + $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
743 +
744 + if (
745 + 'logged_in_users' === $auth_settings['access_who_can_view'] &&
746 + false === apply_filters( 'authorizer_has_access', false, $GLOBALS['wp'] )
747 + ) {
748 + return new \WP_Error(
749 + 'rest_cannot_view',
750 + wp_strip_all_tags( $auth_settings['access_redirect_to_message'] ),
751 + array(
752 + 'status' => 401,
753 + )
754 + );
755 + }
756 + }
757 +
758 + return $errors;
759 + }
760 +
761 +
762 + /**
622 763 * Helper function to determine whether a given email is in one of
623 764 * the lists (pending, approved, blocked). Defaults to the list of
624 765 * approved users.
625 766 *
626 767 * @param string $email Email to check existent of.
627 - * @param string $list List to look for email in.
768 + * @param string $user_list List to look for email in.
628 769 * @param string $multisite_mode Admin context.
629 770 * @return boolean Whether email was found.
630 771 */
631 - public function is_email_in_list( $email = '', $list = 'approved', $multisite_mode = 'single' ) {
772 + public function is_email_in_list( $email = '', $user_list = 'approved', $multisite_mode = 'single' ) {
632 773 if ( empty( $email ) ) {
633 774 return false;
634 775 }
635 776
@@ -634,9 +775,9 @@
634 775 }
635 776
636 777 $options = Options::get_instance();
637 778
638 - switch ( $list ) {
779 + switch ( $user_list ) {
639 780 case 'pending':
640 781 $auth_settings_access_users_pending = $options->get( 'access_users_pending', Helper::SINGLE_CONTEXT );
641 782 return Helper::in_multi_array( $email, $auth_settings_access_users_pending );
642 783 case 'blocked':
@@ -665,10 +806,10 @@
665 806 default:
666 807 if ( 'single' !== $multisite_mode ) {
667 808 // Get multisite users only.
668 809 $auth_settings_access_users_approved = $options->get( 'access_users_approved', Helper::NETWORK_CONTEXT );
669 - } elseif ( is_multisite() && 1 === intval( $options->get( 'advanced_override_multisite' ) ) ) {
670 - // This site has overridden any multisite settings, so only get its users.
810 + } elseif ( is_multisite() && 1 === intval( $options->get( 'advanced_override_multisite' ) ) && empty( $options->get( 'prevent_override_multisite', Helper::NETWORK_CONTEXT ) ) ) {
811 + // This site has overridden any multisite settings (and is not prevented from doing so), so only get its users.
671 812 $auth_settings_access_users_approved = $options->get( 'access_users_approved', Helper::SINGLE_CONTEXT );
672 813 } else {
673 814 // Get all site users and all multisite users.
674 815 $auth_settings_access_users_approved = array_merge(
@@ -678,6 +819,5 @@
678 819 }
679 820 return Helper::in_multi_array( $email, $auth_settings_access_users_approved );
680 821 }
681 822 }
682 -
683 823 }