PluginProbe
Authorizer / 3.15.1
Authorizer v3.15.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
authorizer / src / authorizer / class-authorization.php

class-authorization.php in Authorizer 3.15.1, at src/authorizer/class-authorization.php

972 lines 43.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Authorizer
4 *
5 * @license GPL-2.0+
6 * @link https://github.com/uhm-coe/authorizer
7 * @package authorizer
8 */
9
10 namespace Authorizer;
11
12 use Authorizer\Helper;
13 use Authorizer\Options;
14
15 // Prevent direct access.
16 defined( 'ABSPATH' ) || exit;
17
18 /**
19 * Implements the authorization (roles and permissions) features of the plugin.
20 */
21 class Authorization extends Singleton {
22
23 /**
24 * This function will fail with a wp_die() message to the user if they
25 * don't have access.
26 *
27 * @param WP_User $user User to check.
28 * @param array $user_emails Array of user's plaintext emails (in case current user doesn't have a WP account).
29 * @param array $user_data Array of keys for email, username, first_name, last_name, authenticated_by,
30 * and any of the following based on authentication method:
31 * google_attributes,
32 * cas_attributes, cas_server_id,
33 * ldap_attributes,
34 * oauth2_attributes, oauth2_provider, oauth2_server_id,
35 * oidc_attributes, oidc_server_id.
36 * @return WP_Error|WP_User
37 * WP_Error if there was an error on user creation / adding user to blog.
38 * WP_Error / wp_die() if user does not have access.
39 * WP_User if user has access.
40 */
41 public function check_user_access( $user, $user_emails, $user_data = array() ) {
42 // Grab plugin settings.
43 $options = Options::get_instance();
44 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
45 $auth_settings_access_users_pending = $options->sanitize_user_list(
46 $options->get( 'access_users_pending', Helper::SINGLE_CONTEXT )
47 );
48 $auth_settings_access_users_approved_single = $options->get( 'access_users_approved', Helper::SINGLE_CONTEXT );
49 $auth_settings_access_users_approved_multi = $options->get( 'access_users_approved', Helper::NETWORK_CONTEXT );
50 $auth_settings_access_users_approved = $options->sanitize_user_list(
51 array_merge(
52 $auth_settings_access_users_approved_single,
53 $auth_settings_access_users_approved_multi
54 )
55 );
56
57 // If this is an existing user, update which external service authenticated
58 // them.
59 if ( $user && ! empty( $user_data['authenticated_by'] ) ) {
60 update_user_meta( $user->ID, 'authenticated_by', $user_data['authenticated_by'] );
61 }
62
63 // Get whether to update first/last name on login from the external service
64 // used to authenticate this user.
65 $attr_update_on_login = '';
66 if ( ! empty( $user_data['authenticated_by'] ) ) {
67 $attr_update_on_login_key = '';
68 if ( 'cas' === $user_data['authenticated_by'] ) {
69 $attr_update_on_login_key = empty( $user_data['cas_server_id'] ) || 1 === intval( $user_data['cas_server_id'] ) ? 'cas_attr_update_on_login' : 'cas_attr_update_on_login_' . $user_data['cas_server_id'];
70 } elseif ( 'ldap' === $user_data['authenticated_by'] ) {
71 $attr_update_on_login_key = 'ldap_attr_update_on_login';
72 } elseif ( 'oauth2' === $user_data['authenticated_by'] ) {
73 $attr_update_on_login_key = empty( $user_data['oauth2_server_id'] ) || 1 === intval( $user_data['oauth2_server_id'] ) ? 'oauth2_attr_update_on_login' : 'oauth2_attr_update_on_login_' . $user_data['oauth2_server_id'];
74 } elseif ( 'oidc' === $user_data['authenticated_by'] ) {
75 $attr_update_on_login_key = empty( $user_data['oidc_server_id'] ) || 1 === intval( $user_data['oidc_server_id'] ) ? 'oidc_attr_update_on_login' : 'oidc_attr_update_on_login_' . $user_data['oidc_server_id'];
76 }
77 if ( ! empty( $attr_update_on_login_key ) ) {
78 $attr_update_on_login = ! empty( $auth_settings[ $attr_update_on_login_key ] ) ? $auth_settings[ $attr_update_on_login_key ] : '';
79 }
80 }
81
82 // Detect whether this user's first and last name should be updated below
83 // (if the external service provides a different value, the option is set to
84 // update it, and it's empty if the option to only set it if empty is
85 // enabled).
86 $should_update_first_name =
87 $user && ! empty( $user_data['first_name'] ) && $user_data['first_name'] !== $user->first_name &&
88 ( '1' === $attr_update_on_login || ( 'update-if-empty' === $attr_update_on_login && empty( $user->first_name ) ) );
89
90 $should_update_last_name =
91 $user && ! empty( $user_data['last_name'] ) && $user_data['last_name'] !== $user->last_name &&
92 ( '1' === $attr_update_on_login || ( 'update-if-empty' === $attr_update_on_login && empty( $user->last_name ) ) );
93
94 /**
95 * Filter whether to block the currently logging in user based on any of
96 * their user attributes.
97 *
98 * @param bool $allow_login Whether to block the currently logging in user.
99 * @param array $user_data User data returned from external service.
100 */
101 $allow_login = apply_filters( 'authorizer_allow_login', true, $user_data );
102 $blocked_by_filter = ! $allow_login; // Use this for better readability.
103
104 // Check our externally authenticated user against the block list.
105 // If any of their email addresses are blocked, set the relevant user
106 // meta field, and show them an error screen.
107 foreach ( $user_emails as $user_email ) {
108 if ( $blocked_by_filter || $this->is_email_in_list( $user_email, 'blocked' ) ) {
109
110 // Add user to blocked list if it was blocked via the filter.
111 if ( $blocked_by_filter && ! $this->is_email_in_list( $user_email, 'blocked' ) ) {
112 $auth_settings_access_users_blocked = $options->sanitize_user_list(
113 $options->get( 'access_users_blocked', Helper::SINGLE_CONTEXT )
114 );
115 array_push(
116 $auth_settings_access_users_blocked,
117 array(
118 'email' => Helper::lowercase( $user_email ),
119 'date_added' => wp_date( 'M Y' ),
120 )
121 );
122 update_option( 'auth_settings_access_users_blocked', $auth_settings_access_users_blocked, false );
123 }
124
125 // If the blocked external user has a WordPress account, mark it as
126 // blocked (enforce block in this->authenticate()).
127 if ( $user ) {
128 update_user_meta( $user->ID, 'auth_blocked', 'yes' );
129 }
130
131 // Allow overriding the message blocked users see after logging in.
132 if ( defined( 'AUTHORIZER_LOGIN_MESSAGE_BLOCKED_USERS' ) ) {
133 $auth_settings['access_blocked_redirect_to_message'] = \AUTHORIZER_LOGIN_MESSAGE_BLOCKED_USERS;
134 }
135 /**
136 * Filters the message blocked users see after logging in.
137 *
138 * @since 3.12.0
139 *
140 * @param string $message The message content.
141 */
142 $auth_settings['access_blocked_redirect_to_message'] = apply_filters( 'authorizer_login_message_blocked_users', $auth_settings['access_blocked_redirect_to_message'] );
143
144 // Notify user about blocked status and return without authenticating them.
145 // phpcs:ignore WordPress.Security.NonceVerification
146 $redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : home_url();
147 $page_title = sprintf(
148 /* TRANSLATORS: %s: Name of blog */
149 __( '%s - Access Restricted', 'authorizer' ),
150 get_bloginfo( 'name' )
151 );
152 $error_message =
153 apply_filters( 'the_content', $auth_settings['access_blocked_redirect_to_message'] ) .
154 '<hr />' .
155 '<p style="text-align: center;">' .
156 '<a class="button" href="' . wp_logout_url( $redirect_to ) . '">' .
157 __( 'Back', 'authorizer' ) .
158 '</a></p>';
159 update_option( 'auth_settings_advanced_login_error', $error_message, false );
160 wp_die( wp_kses( $error_message, Helper::$allowed_html ), esc_html( $page_title ) );
161 return new \WP_Error( 'invalid_login', __( 'Invalid login attempted.', 'authorizer' ) );
162 }
163 }
164
165 // Get the default role for this user (or their current role, if they
166 // already have an account).
167 $default_role = $user && is_array( $user->roles ) && count( $user->roles ) > 0 ? $user->roles[0] : $auth_settings['access_default_role'];
168 /**
169 * Filter the role of the user currently logging in. The role will be
170 * set to the default (specified in Authorizer options) for new users,
171 * or the user's current role for existing users. This filter allows
172 * changing user roles based on custom CAS/LDAP attributes.
173 *
174 * @param string $role Role of the user currently logging in.
175 * @param array $user_data User data returned from external service.
176 * @param WP_User|false|null|WP_Error $user User object if logging in user exists.
177 *
178 * @return string|array Role of the user currently logging in, or an array with keys 'default_role' (string), 'roles_to_add' (array), and 'roles_to_remove' (array) if support for multiple roles is desired.
179 */
180 $approved_role = apply_filters( 'authorizer_custom_role', $default_role, $user_data, $user );
181
182 // Support for multiple roles if supplied in the filter above. Note: this
183 // only has partial support for multisite (it will only add/remove roles
184 // to the current blog, not all blogs).
185 $roles_to_add = empty( $approved_role['roles_to_add'] ) ? array() : $approved_role['roles_to_add'];
186 $roles_to_remove = empty( $approved_role['roles_to_remove'] ) ? array() : $approved_role['roles_to_remove'];
187 if ( ! empty( $approved_role['default_role'] ) ) {
188 $approved_role = $approved_role['default_role'];
189 }
190
191 /**
192 * Filter whether to automatically approve the currently logging in user
193 * based on any of their user attributes.
194 *
195 * @param bool $automatically_approve_login
196 * Whether to automatically approve the currently logging in user.
197 * @param array $user_data User data returned from external service.
198 * @param WP_User|false|null|WP_Error $user User object if logging in user exists.
199 */
200 $automatically_approve_login = apply_filters( 'authorizer_automatically_approve_login', false, $user_data, $user );
201
202 // If this externally-authenticated user is an existing administrator (admin
203 // in single site mode, or super admin in network mode), and isn't blocked,
204 // let them in. Update their first/last name if needed.
205 if ( $user && is_super_admin( $user->ID ) ) {
206 if ( $should_update_first_name ) {
207 update_user_meta( $user->ID, 'first_name', $user_data['first_name'] );
208 }
209 if ( $should_update_last_name ) {
210 update_user_meta( $user->ID, 'last_name', $user_data['last_name'] );
211 }
212
213 return $user;
214 }
215
216 // Iterate through each of the email addresses provided by the external
217 // service and determine if any of them have access.
218 $last_email = end( $user_emails );
219 reset( $user_emails );
220 foreach ( $user_emails as $user_email ) {
221 $is_newly_approved_user = false;
222
223 // If this externally authenticated user isn't in the approved list
224 // and login access is set to "All authenticated users," or if they were
225 // automatically approved in the "authorizer_approve_login" filter
226 // above, then add them to the approved list (they'll get an account
227 // created below if they don't have one yet).
228 if (
229 ! $this->is_email_in_list( $user_email, 'approved' ) &&
230 ( 'external_users' === $auth_settings['access_who_can_login'] || $automatically_approve_login )
231 ) {
232 $is_newly_approved_user = true;
233
234 // If this user happens to be in the pending list (rare),
235 // remove them from pending before adding them to approved.
236 if ( $this->is_email_in_list( $user_email, 'pending' ) ) {
237 foreach ( $auth_settings_access_users_pending as $key => $pending_user ) {
238 if ( 0 === strcasecmp( $pending_user['email'], $user_email ) ) {
239 unset( $auth_settings_access_users_pending[ $key ] );
240 update_option( 'auth_settings_access_users_pending', $auth_settings_access_users_pending, false );
241 break;
242 }
243 }
244 }
245
246 // Add this user to the approved list.
247 $approved_user = array(
248 'email' => Helper::lowercase( $user_email ),
249 'role' => $approved_role,
250 'date_added' => wp_date( 'Y-m-d H:i:s' ),
251 );
252 array_push( $auth_settings_access_users_approved, $approved_user );
253 array_push( $auth_settings_access_users_approved_single, $approved_user );
254 update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved_single, false );
255 }
256
257 // Check our externally authenticated user against the approved
258 // list. If they are approved, log them in (and create their account
259 // if necessary).
260 if ( $is_newly_approved_user || $this->is_email_in_list( $user_email, 'approved' ) ) {
261 $user_info = $is_newly_approved_user ? $approved_user : Helper::get_user_info_from_list( $user_email, $auth_settings_access_users_approved );
262
263 // If this user's role was modified above (in the authorizer_custom_role
264 // filter), update the role in the approved list and use that role
265 // (i.e., if the roles are out of sync, use the authorizer_custom_role
266 // value instead of the role in the approved list).
267 if ( has_filter( 'authorizer_custom_role' ) ) {
268 $user_info['role'] = $approved_role;
269
270 // Find the user in either the single site or multisite approved list
271 // and update their role there if different.
272 foreach ( $auth_settings_access_users_approved_single as $index => $auth_settings_access_user_approved_single ) {
273 if ( $user_info['email'] === $auth_settings_access_user_approved_single['email'] ) {
274 if ( $auth_settings_access_users_approved_single[ $index ]['role'] !== $approved_role ) {
275 $auth_settings_access_users_approved_single[ $index ]['role'] = $approved_role;
276 update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved_single, false );
277 }
278 break;
279 }
280 }
281 if ( is_multisite() ) {
282 foreach ( $auth_settings_access_users_approved_multi as $index => $auth_settings_access_user_approved_multi ) {
283 if ( $user_info['email'] === $auth_settings_access_user_approved_multi['email'] ) {
284 if ( $auth_settings_access_users_approved_multi[ $index ]['role'] !== $approved_role ) {
285 $auth_settings_access_users_approved_multi[ $index ]['role'] = $approved_role;
286 update_blog_option( get_main_site_id( get_main_network_id() ), 'auth_multisite_settings_access_users_approved', $auth_settings_access_users_approved_multi );
287 }
288 break;
289 }
290 }
291 }
292 }
293
294 // If the approved external user does not have a WordPress account, create it.
295 if ( ! $user ) {
296 if ( array_key_exists( 'username', $user_data ) ) {
297 $username = $user_data['username'];
298 } else {
299 $username = explode( '@', $user_info['email'] );
300 $username = $username[0];
301 }
302 // If there's already a user with this username (e.g.,
303 // johndoe/johndoe@gmail.com exists, and we're trying to add
304 // johndoe/johndoe@example.com), try appending digits to the end until
305 // a free username is found (e.g., johndoe2).
306 $username = Helper::ensure_unique_username( $username );
307 $result = wp_insert_user(
308 array(
309 'user_login' => strtolower( $username ),
310 'user_pass' => wp_generate_password(), // random password.
311 'first_name' => array_key_exists( 'first_name', $user_data ) ? $user_data['first_name'] : '',
312 'last_name' => array_key_exists( 'last_name', $user_data ) ? $user_data['last_name'] : '',
313 'user_email' => Helper::lowercase( $user_info['email'] ),
314 'user_registered' => wp_date( 'Y-m-d H:i:s' ),
315 'role' => $user_info['role'],
316 )
317 );
318
319 // Fail with message if error.
320 if ( is_wp_error( $result ) || 0 === $result ) {
321 return $result;
322 }
323
324 // Authenticate as new user.
325 $user = new \WP_User( $result );
326
327 /**
328 * Fires after an external user is authenticated for the first time
329 * and a new WordPress account is created for them.
330 *
331 * @since 2.8.0
332 *
333 * @param WP_User $user User object.
334 * @param array $user_data User data from external service.
335 *
336 * Example $user_data:
337 * array(
338 * 'email' => 'user@example.edu',
339 * 'username' => 'user',
340 * 'first_name' => 'First',
341 * 'last_name' => 'Last',
342 * 'authenticated_by' => 'cas',
343 * 'cas_attributes' => array( ... ),
344 * );
345 */
346 do_action( 'authorizer_user_register', $user, $user_data );
347
348 // Save which external service authenticated this new user to user meta.
349 if ( $user && ! empty( $user_data['authenticated_by'] ) ) {
350 update_user_meta( $user->ID, 'authenticated_by', $user_data['authenticated_by'] );
351 }
352
353 // If multisite, iterate through all sites in the network and add the user
354 // currently logging in to any of them that have the user on the approved list.
355 // Note: this is useful for first-time logins--some users will have access
356 // to multiple sites, and this prevents them from having to log into each
357 // site individually to get access.
358 if ( is_multisite() ) {
359 $site_ids_of_user = array_map(
360 function ( $site_of_user ) {
361 return intval( $site_of_user->userblog_id );
362 },
363 get_blogs_of_user( $user->ID )
364 );
365
366 // phpcs:ignore WordPress.WP.DeprecatedFunctions.wp_get_sitesFound
367 $sites = function_exists( 'get_sites' ) ? get_sites() : wp_get_sites( array( 'limit' => PHP_INT_MAX ) );
368 foreach ( $sites as $site ) {
369 $blog_id = function_exists( 'get_sites' ) ? $site->blog_id : $site['blog_id'];
370
371 // Skip if user is already added to this site.
372 if ( in_array( intval( $blog_id ), $site_ids_of_user, true ) ) {
373 continue;
374 }
375
376 // Check if user is on the approved list of this site they are not added to.
377 $other_auth_settings_access_users_approved = get_blog_option( $blog_id, 'auth_settings_access_users_approved', array() );
378 if ( Helper::in_multi_array( $user->user_email, $other_auth_settings_access_users_approved ) ) {
379 $other_user_info = Helper::get_user_info_from_list( $user->user_email, $other_auth_settings_access_users_approved );
380 // Add user to other site.
381 add_user_to_blog( $blog_id, $user->ID, $other_user_info['role'] );
382 }
383 }
384 }
385
386 // Check if this new user has any preassigned usermeta
387 // values in their approved list entry, and apply them to
388 // their new WordPress account.
389 if ( array_key_exists( 'usermeta', $user_info ) && is_array( $user_info['usermeta'] ) ) {
390 $meta_key = $options->get( 'advanced_usermeta' );
391
392 if ( array_key_exists( 'meta_key', $user_info['usermeta'] ) && array_key_exists( 'meta_value', $user_info['usermeta'] ) ) {
393 // Only update the usermeta if the stored value matches
394 // the option set in authorizer settings (if they don't
395 // match it's probably old data).
396 if ( $meta_key === $user_info['usermeta']['meta_key'] ) {
397 // Update user's usermeta value for usermeta key stored in authorizer options.
398 if ( strpos( $meta_key, 'acf___' ) === 0 && class_exists( 'acf' ) ) {
399 // We have an ACF field value, so use the ACF function to update it.
400 update_field( str_replace( 'acf___', '', $meta_key ), $user_info['usermeta']['meta_value'], 'user_' . $user->ID );
401 } else {
402 // We have a normal usermeta value, so just update it via the WordPress function.
403 update_user_meta( $user->ID, $meta_key, $user_info['usermeta']['meta_value'] );
404 }
405 }
406 } elseif ( is_multisite() && count( $user_info['usermeta'] ) > 0 ) {
407 // Update usermeta for each multisite blog defined for this user.
408 foreach ( $user_info['usermeta'] as $blog_id => $usermeta ) {
409 if ( array_key_exists( 'meta_key', $usermeta ) && array_key_exists( 'meta_value', $usermeta ) ) {
410 // Add this new user to the blog before we create their user meta (this step typically happens below, but we need it to happen early so we can create user meta here).
411 if ( ! is_user_member_of_blog( $user->ID, $blog_id ) ) {
412 add_user_to_blog( $blog_id, $user->ID, $user_info['role'] );
413 }
414 switch_to_blog( $blog_id );
415 // Update user's usermeta value for usermeta key stored in authorizer options.
416 if ( strpos( $meta_key, 'acf___' ) === 0 && class_exists( 'acf' ) ) {
417 // We have an ACF field value, so use the ACF function to update it.
418 update_field( str_replace( 'acf___', '', $meta_key ), $usermeta['meta_value'], 'user_' . $user->ID );
419 } else {
420 // We have a normal usermeta value, so just update it via the WordPress function.
421 update_user_meta( $user->ID, $meta_key, $usermeta['meta_value'] );
422 }
423 restore_current_blog();
424 }
425 }
426 }
427 }
428 } else {
429 // Update first/last name from CAS/LDAP if needed.
430 if ( $should_update_first_name ) {
431 update_user_meta( $user->ID, 'first_name', $user_data['first_name'] );
432 }
433 if ( $should_update_last_name ) {
434 update_user_meta( $user->ID, 'last_name', $user_data['last_name'] );
435 }
436 }
437
438 // If this is multisite, add new user to current blog.
439 if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) {
440 $result = add_user_to_blog( get_current_blog_id(), $user->ID, $user_info['role'] );
441
442 // Fail with message if error.
443 if ( is_wp_error( $result ) ) {
444 return $result;
445 }
446 }
447
448 // Ensure user has the same role as their entry in the approved list.
449 // Note: if any additional roles are defined to be added or removed,
450 // use add_role() instead of set_role() so we retain existing roles.
451 if ( $user_info && ! in_array( $user_info['role'], $user->roles, true ) ) {
452 if ( empty( $roles_to_add ) && empty( $roles_to_remove ) ) {
453 $user->set_role( $user_info['role'] );
454 } else {
455 $user->add_role( $user_info['role'] );
456 }
457 }
458
459 /**
460 * Filter additional roles to add to the user currently logging in. This
461 * filter allows changing user roles based on custom CAS/LDAP attributes.
462 *
463 * @param array $roles_to_add Roles to add to the user currently logging in.
464 * @param array $user_data User data returned from external service.
465 * @param WP_User|false|null|WP_Error $user User object if logging in user exists.
466 */
467 $roles_to_add = apply_filters( 'authorizer_custom_roles_to_add', $roles_to_add, $user_data, $user );
468
469 // Add additional roles to the user. Note: this only has partial support
470 // for multisite (it will only add roles to the current blog, not all blogs).
471 if ( ! empty( $roles_to_add ) ) {
472 foreach ( $roles_to_add as $role_to_add ) {
473 $user->add_role( $role_to_add );
474 }
475 }
476
477 /**
478 * Filter additional roles to remove from the user currently logging in. This
479 * filter allows changing user roles based on custom CAS/LDAP attributes.
480 *
481 * @param array $roles_to_remove Roles to remove from the user currently logging in.
482 * @param array $user_data User data returned from external service.
483 * @param WP_User|false|null|WP_Error $user User object if logging in user exists.
484 */
485 $roles_to_remove = apply_filters( 'authorizer_custom_roles_to_remove', $roles_to_remove, $user_data, $user );
486
487 // Remove roles from the user. Note: this only has partial support for
488 // multisite (it will only remove roles from the current blog, not all blogs).
489 if ( ! empty( $roles_to_remove ) ) {
490 foreach ( $roles_to_remove as $role_to_remove ) {
491 $user->remove_role( $role_to_remove );
492 }
493 }
494
495 return $user;
496
497 } elseif ( 0 === strcasecmp( $user_email, $last_email ) ) {
498 /**
499 * Note: only do this for the last email address we are checking (we need
500 * to iterate through them all to make sure one of them isn't approved).
501 */
502
503 // User isn't an admin, is not blocked, and is not approved.
504 // Add them to the pending list and notify them and their instructor.
505 if ( strlen( $user_email ) > 0 && ! $this->is_email_in_list( $user_email, 'pending' ) ) {
506 $pending_user = array();
507 $pending_user['email'] = Helper::lowercase( $user_email );
508 $pending_user['role'] = $approved_role;
509 $pending_user['date_added'] = '';
510 array_push( $auth_settings_access_users_pending, $pending_user );
511 update_option( 'auth_settings_access_users_pending', $auth_settings_access_users_pending, false );
512
513 // Create strings used in the email notification.
514 $site_name = get_bloginfo( 'name' );
515 $site_url = get_bloginfo( 'url' );
516 $authorizer_options_url = 'settings' === $auth_settings['advanced_admin_menu'] ? admin_url( 'options-general.php?page=authorizer' ) : admin_url( '?page=authorizer' );
517
518 // Notify users with the role specified in "Which role should
519 // receive email notifications about pending users?" and any
520 // individual users specified in "Which users should receive email
521 // notifications about pending users?".
522 if ( strlen( $auth_settings['access_role_receive_pending_emails'] ) > 0 || ! empty( $auth_settings['access_users_receive_pending_emails'] ) ) {
523 $emails_to_notify = array();
524 // Add users with specified role (if any).
525 if ( strlen( $auth_settings['access_role_receive_pending_emails'] ) > 0 ) {
526 foreach ( get_users( array( 'role' => $auth_settings['access_role_receive_pending_emails'] ) ) as $user_recipient ) {
527 if ( ! empty( $user_recipient->user_email ) ) {
528 $emails_to_notify[] = $user_recipient->user_email;
529 }
530 }
531 }
532 // Add individual users (if any).
533 if ( ! empty( $auth_settings['access_users_receive_pending_emails'] ) ) {
534 foreach ( $auth_settings['access_users_receive_pending_emails'] as $username ) {
535 $user_recipient = get_user_by( 'login', $username );
536 if ( ! empty( $user_recipient->user_email ) ) {
537 $emails_to_notify[] = $user_recipient->user_email;
538 }
539 }
540 }
541 // Remove any duplicate email addresses (a user could potentially be
542 // added via their role and again via their username).
543 $emails_to_notify = array_unique( $emails_to_notify );
544 // Email each recipient.
545 if ( count( $emails_to_notify ) > 0 ) {
546 foreach ( $emails_to_notify as $email ) {
547 wp_mail(
548 $email,
549 sprintf(
550 /* TRANSLATORS: 1: User email 2: Name of site */
551 __( 'Action required: Pending user %1$s at %2$s', 'authorizer' ),
552 $pending_user['email'],
553 $site_name
554 ),
555 sprintf(
556 /* TRANSLATORS: 1: Name of site 2: URL of site 3: URL of authorizer */
557 __( "A new user has tried to access the %1\$s site you manage at:\n%2\$s\n\nPlease log in to approve or deny their request:\n%3\$s\n", 'authorizer' ),
558 $site_name,
559 $site_url,
560 $authorizer_options_url
561 )
562 );
563 }
564 }
565 }
566 }
567
568 // Fetch the external service this user authenticated with, and append
569 // it to the logout URL below (so we can fire custom logout routines in
570 // custom_logout() based on their external service. This is necessary
571 // because a pending user does not have a WP_User, and thus no
572 // "authenticated_by" usermeta that is normally used to do this.
573 $external_param = isset( $user_data['authenticated_by'] ) ? '&external=' . $user_data['authenticated_by'] : '';
574
575 // Allow overriding the message pending users see after logging in.
576 if ( defined( 'AUTHORIZER_LOGIN_MESSAGE_PENDING_USERS' ) ) {
577 $auth_settings['access_pending_redirect_to_message'] = \AUTHORIZER_LOGIN_MESSAGE_PENDING_USERS;
578 }
579 /**
580 * Filters the message pending users see after logging in.
581 *
582 * @since 3.12.0
583 *
584 * @param string $message The message content.
585 */
586 $auth_settings['access_pending_redirect_to_message'] = apply_filters( 'authorizer_login_message_pending_users', $auth_settings['access_pending_redirect_to_message'] );
587
588 // Notify user about pending status and return without authenticating them.
589 // phpcs:ignore WordPress.Security.NonceVerification
590 $redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : home_url();
591 $page_title = get_bloginfo( 'name' ) . ' - Access Pending';
592 $error_message =
593 apply_filters( 'the_content', $auth_settings['access_pending_redirect_to_message'] ) .
594 '<hr />' .
595 '<p style="text-align: center;">' .
596 '<a class="button" href="' . wp_logout_url( $redirect_to ) . $external_param . '">' .
597 __( 'Back', 'authorizer' ) .
598 '</a></p>';
599 update_option( 'auth_settings_advanced_login_error', $error_message, false );
600 wp_die( wp_kses( $error_message, Helper::$allowed_html ), esc_html( $page_title ) );
601 }
602 }
603
604 // Sanity check: if we made it here without returning, something has gone wrong.
605 return new \WP_Error( 'invalid_login', __( 'Invalid login attempted.', 'authorizer' ) );
606 }
607
608
609 /**
610 * Restrict access to WordPress site based on settings (everyone, logged_in_users).
611 *
612 * Action: parse_request
613 *
614 * @param WP $wp WordPress object.
615 * @return WP|void WP object when passing through to WordPress authentication, or void.
616 */
617 public function restrict_access( $wp ) {
618 // Grab plugin settings.
619 $options = Options::get_instance();
620 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
621
622 // Grab current user.
623 $current_user = wp_get_current_user();
624
625 $has_access = (
626 // Always allow access if WordPress is installing.
627 // phpcs:ignore WordPress.Security.NonceVerification
628 ( defined( 'WP_INSTALLING' ) && isset( $_GET['key'] ) ) ||
629 // Always allow access to admins.
630 ( current_user_can( 'create_users' ) ) ||
631 // Allow access if option is set to 'everyone'.
632 ( 'everyone' === $auth_settings['access_who_can_view'] ) ||
633 // Allow access to approved external users and logged in users if option is set to 'logged_in_users'.
634 ( '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' ) ) ||
635 // Allow REST API requests (access is determined later in the rest_authentication_errors hook).
636 // See: https://github.com/WordPress/WordPress/blob/8e41746cb11271d063608a63e3f6091a8685e677/wp-includes/rest-api.php#L131-L133.
637 ( ! empty( $GLOBALS['wp']->query_vars['rest_route'] ) )
638 );
639
640 /**
641 * Developers can use the `authorizer_has_access` filter to override
642 * restricted access on certain pages. Note that the restriction checks
643 * happens before WordPress executes any queries, so use the $wp variable
644 * to investigate what the visitor is trying to load.
645 *
646 * For example, to unblock an RSS feed, place the following PHP code in
647 * the theme's functions.php file or in a simple plug-in:
648 *
649 * function my_feed_access_override( $has_access, $wp ) {
650 * // Check query variables to see if this is the feed.
651 * if ( ! empty( $wp->query_vars['feed'] ) ) {
652 * $has_access = true;
653 * }
654 *
655 * return $has_access;
656 * }
657 * add_filter( 'authorizer_has_access', 'my_feed_access_override', 10, 2 );
658 */
659 if ( apply_filters( 'authorizer_has_access', $has_access, $wp ) === true ) {
660 // Turn off the public notice about browsing anonymously.
661 update_option( 'auth_settings_advanced_public_notice', false, true );
662
663 // We've determined that the current user has access, so simply return to grant access.
664 return $wp;
665 }
666
667 // Allow HEAD requests to the root (usually discovery from a REST client).
668 if ( ! empty( $_SERVER['REQUEST_METHOD'] ) && 'HEAD' === $_SERVER['REQUEST_METHOD'] && empty( $wp->request ) && empty( $wp->matched_query ) ) {
669 return $wp;
670 }
671
672 /* We've determined that the current user doesn't have access, so we deal with them now. */
673
674 // Fringe case: In a multisite, a user of a different blog can successfully
675 // log in, but they aren't on the 'approved' whitelist for this blog.
676 // If that's the case, add them to the pending list for this blog.
677 if ( is_multisite() && is_user_logged_in() && ! $has_access ) {
678 $current_user = wp_get_current_user();
679
680 // Check user access; block if not, add them to pending list if open, let them through otherwise.
681 $result = $this->check_user_access( $current_user, array( $current_user->user_email ) );
682 }
683
684 // Check to see if the requested page is public. If so, show it.
685 if ( empty( $wp->request ) ) {
686 $current_page_id = 'home';
687 } else {
688 $request_query = isset( $wp->query_vars ) ? new \WP_Query( $wp->query_vars ) : null;
689 $current_page_id = isset( $request_query->post_count ) && $request_query->post_count > 0 ? $request_query->post->ID : '';
690 }
691 if ( ! array_key_exists( 'access_public_pages', $auth_settings ) || ! is_array( $auth_settings['access_public_pages'] ) ) {
692 $auth_settings['access_public_pages'] = array();
693 }
694 if ( in_array( strval( $current_page_id ), $auth_settings['access_public_pages'], true ) ) {
695 if ( 'no_warning' === $auth_settings['access_public_warning'] ) {
696 update_option( 'auth_settings_advanced_public_notice', false, true );
697 } else {
698 update_option( 'auth_settings_advanced_public_notice', true, true );
699 }
700 return $wp;
701 }
702
703 // Check to see if any category assigned to the requested page is public. If so, show it.
704 $current_page_categories = wp_get_post_categories( $current_page_id, array( 'fields' => 'slugs' ) );
705 foreach ( $current_page_categories as $current_page_category ) {
706 if ( in_array( 'cat_' . $current_page_category, $auth_settings['access_public_pages'], true ) ) {
707 if ( 'no_warning' === $auth_settings['access_public_warning'] ) {
708 update_option( 'auth_settings_advanced_public_notice', false, true );
709 } else {
710 update_option( 'auth_settings_advanced_public_notice', true, true );
711 }
712 return $wp;
713 }
714 }
715
716 // Check to see if this page can't be found. If so, allow showing the 404 page.
717 if ( strlen( $current_page_id ) < 1 ) {
718 if ( in_array( 'auth_public_404', $auth_settings['access_public_pages'], true ) ) {
719 if ( 'no_warning' === $auth_settings['access_public_warning'] ) {
720 update_option( 'auth_settings_advanced_public_notice', false, true );
721 } else {
722 update_option( 'auth_settings_advanced_public_notice', true, true );
723 }
724 return $wp;
725 }
726 }
727
728 // Check to see if the requested category is public. If so, show it.
729 $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'] : '';
730 if ( $current_category_name ) {
731 $current_category_name_pieces = explode( '/', $current_category_name );
732 $current_category_name = end( $current_category_name_pieces );
733 if ( in_array( 'cat_' . $current_category_name, $auth_settings['access_public_pages'], true ) ) {
734 if ( 'no_warning' === $auth_settings['access_public_warning'] ) {
735 update_option( 'auth_settings_advanced_public_notice', false, true );
736 } else {
737 update_option( 'auth_settings_advanced_public_notice', true, true );
738 }
739 return $wp;
740 }
741 }
742
743 // Allow overriding the message anonymous users see.
744 if ( defined( 'AUTHORIZER_MESSAGE_ANONYMOUS_USERS' ) ) {
745 $auth_settings['access_redirect_to_message'] = \AUTHORIZER_MESSAGE_ANONYMOUS_USERS;
746 }
747 /**
748 * Filters the message anonymous users see when visiting public pages on a private site.
749 *
750 * @since 3.12.0
751 *
752 * @param string $message The message content.
753 */
754 $auth_settings['access_redirect_to_message'] = apply_filters( 'authorizer_message_anonymous_users', $auth_settings['access_redirect_to_message'] );
755
756 // User is denied access, so show them the error message. Render as JSON
757 // if this is a REST API call; otherwise, show the error message via
758 // wp_die() (rendered html), or redirect to the login URL.
759 $current_path = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : home_url();
760 if ( property_exists( $wp, 'matched_query' ) && stripos( $wp->matched_query, 'rest_route=' ) === 0 && 'GET' === $_SERVER['REQUEST_METHOD'] ) {
761 wp_send_json(
762 array(
763 'code' => 'rest_cannot_view',
764 'message' => wp_strip_all_tags( $auth_settings['access_redirect_to_message'] ),
765 'data' => array(
766 'status' => 401,
767 ),
768 )
769 );
770 } elseif ( 'message' === $auth_settings['access_redirect'] ) {
771 $page_title = sprintf(
772 /* TRANSLATORS: %s: Name of blog */
773 __( '%s - Access Restricted', 'authorizer' ),
774 get_bloginfo( 'name' )
775 );
776 $error_message =
777 apply_filters( 'the_content', $auth_settings['access_redirect_to_message'] ) .
778 '<hr />' .
779 '<p style="text-align: center;margin-bottom: -15px;">' .
780 '<a class="button" href="' . wp_login_url( $current_path ) . '">' .
781 __( 'Log In', 'authorizer' ) .
782 '</a></p>';
783 wp_die( wp_kses( $error_message, Helper::$allowed_html ), esc_html( $page_title ) );
784 } else {
785 wp_safe_redirect( wp_login_url( $current_path ), 302 );
786 exit;
787 }
788
789 // Sanity check: we should never get here.
790 wp_die( '<p>Access denied.</p>', 'Site Access Restricted' );
791 }
792
793 /**
794 * If we're showing search results or a post listing (home or archive page) to
795 * an anonymous user, and Authorizer is configured to only allow logged in
796 * users to see the site, filter the query to only posts marked public.
797 *
798 * Action: pre_get_posts
799 *
800 * @param WP_Query $query The WP_Query instance (passed by reference).
801 * @return void
802 */
803 public function remove_private_pages_from_search_and_archives( $query ) {
804 // It's possible for pre_get_posts to fire before wp-includes/pluggable.php
805 // is loaded, so verify before using the is_user_logged_in() function.
806 if ( ! function_exists( 'is_user_logged_in' ) ) {
807 require ABSPATH . WPINC . '/pluggable.php';
808 }
809
810 // Fix for edge case when viewing admin pages in Pressbooks (Undefined
811 // constant "SECURE_AUTH_COOKIE").
812 if ( ! defined( 'SECURE_AUTH_COOKIE' ) ) {
813 wp_cookie_constants();
814 }
815
816 // Do nothing if user is logged in, this isn't the main query, or we're not
817 // showing search results, home page, or an archive page.
818 if (
819 is_user_logged_in() || ! $query->is_main_query() ||
820 ! ( $query->is_search() || $query->is_home() || $query->is_archive() )
821 ) {
822 return;
823 }
824
825 $options = Options::get_instance();
826 $who_can_view = $options->get( 'access_who_can_view' );
827 $public_pages = $options->get( 'access_public_pages' );
828 $public_pages = is_array( $public_pages ) ? $public_pages : array();
829
830 // Do nothing if this site isn't restricted to logged in users only.
831 if ( 'logged_in_users' !== $who_can_view ) {
832 return;
833 }
834
835 // Check for special public types (home, 404, categories).
836 $public_category_ids = array();
837 foreach ( $public_pages as $index => $public_page ) {
838 if ( 'home' === $public_page || 'auth_public_404' === $public_page ) {
839 unset( $public_pages[ $index ] );
840 } elseif ( 'cat_' === substr( $public_page, 0, 4 ) ) {
841 $public_category_name = substr( $public_page, 4 );
842 unset( $public_pages[ $index ] );
843 $public_category_ids[] = get_cat_ID( $public_category_name );
844 }
845 }
846 if ( ! empty( $public_category_ids ) ) {
847 $pages_in_public_categories = get_posts( array(
848 'posts_per_page' => -1,
849 'fields' => 'ids',
850 'category__in' => $public_category_ids,
851 ) );
852 $public_pages = array_merge( $public_pages, $pages_in_public_categories );
853 }
854
855 $query->set( 'post__in', $public_pages );
856 }
857
858 /**
859 * Prevent REST API access if user isn't authenticated and "only logged in
860 * users can see the site" is enabled.
861 *
862 * Filter: rest_authentication_errors
863 *
864 * @param WP_Error|null|true $errors WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded.
865 * @return WP_Error|null|true WP_Error if not logged in and "only logged in users can see the site" is enabled.
866 */
867 public function restrict_rest_api( $errors ) {
868 // If there is already an error, just return that.
869 if ( ! empty( $errors ) ) {
870 return $errors;
871 }
872
873 // If user isn't logged in, check for "only logged in users can see the site".
874 if ( ! is_user_logged_in() ) {
875 // Grab plugin settings.
876 $options = Options::get_instance();
877 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
878
879 if (
880 'logged_in_users' === $auth_settings['access_who_can_view'] &&
881 false === apply_filters( 'authorizer_has_access', false, $GLOBALS['wp'] )
882 ) {
883 // Allow overriding the message anonymous users see.
884 if ( defined( 'AUTHORIZER_MESSAGE_ANONYMOUS_USERS' ) ) {
885 $auth_settings['access_redirect_to_message'] = \AUTHORIZER_MESSAGE_ANONYMOUS_USERS;
886 }
887 /**
888 * Filters the message anonymous users see when visiting public pages on a private site.
889 *
890 * @since 3.12.0
891 *
892 * @param string $message The message content.
893 */
894 $auth_settings['access_redirect_to_message'] = apply_filters( 'authorizer_message_anonymous_users', $auth_settings['access_redirect_to_message'] );
895
896 return new \WP_Error(
897 'rest_cannot_view',
898 wp_strip_all_tags( $auth_settings['access_redirect_to_message'] ),
899 array(
900 'status' => 401,
901 )
902 );
903 }
904 }
905
906 return $errors;
907 }
908
909
910 /**
911 * Helper function to determine whether a given email is in one of
912 * the lists (pending, approved, blocked). Defaults to the list of
913 * approved users.
914 *
915 * @param string $email Email to check existent of.
916 * @param string $user_list List to look for email in.
917 * @param string $multisite_mode Admin context.
918 * @return boolean Whether email was found.
919 */
920 public function is_email_in_list( $email = '', $user_list = 'approved', $multisite_mode = 'single' ) {
921 if ( empty( $email ) ) {
922 return false;
923 }
924
925 $options = Options::get_instance();
926
927 switch ( $user_list ) {
928 case 'pending':
929 $auth_settings_access_users_pending = $options->get( 'access_users_pending', Helper::SINGLE_CONTEXT );
930 return Helper::in_multi_array( $email, $auth_settings_access_users_pending );
931 case 'blocked':
932 $auth_settings_access_users_blocked = $options->get( 'access_users_blocked', Helper::SINGLE_CONTEXT );
933 // Blocked list can have wildcard matches, e.g., @baddomain.com, which
934 // should match any email address at that domain. Check if any wildcards
935 // exist, and if the email address has that domain.
936 $email_in_blocked_domain = false;
937 $blocked_domains = preg_grep(
938 '/^@.*/',
939 array_map(
940 function ( $blocked_item ) {
941 return $blocked_item['email']; },
942 $auth_settings_access_users_blocked
943 )
944 );
945 foreach ( $blocked_domains as $blocked_domain ) {
946 $email_domain = substr( $email, strrpos( $email, '@' ) );
947 if ( $email_domain === $blocked_domain ) {
948 $email_in_blocked_domain = true;
949 break;
950 }
951 }
952 return $email_in_blocked_domain || Helper::in_multi_array( $email, $auth_settings_access_users_blocked );
953 case 'approved':
954 default:
955 if ( 'single' !== $multisite_mode ) {
956 // Get multisite users only.
957 $auth_settings_access_users_approved = $options->get( 'access_users_approved', Helper::NETWORK_CONTEXT );
958 } elseif ( is_multisite() && 1 === intval( $options->get( 'advanced_override_multisite' ) ) && empty( $options->get( 'prevent_override_multisite', Helper::NETWORK_CONTEXT ) ) ) {
959 // This site has overridden any multisite settings (and is not prevented from doing so), so only get its users.
960 $auth_settings_access_users_approved = $options->get( 'access_users_approved', Helper::SINGLE_CONTEXT );
961 } else {
962 // Get all site users and all multisite users.
963 $auth_settings_access_users_approved = array_merge(
964 $options->get( 'access_users_approved', Helper::SINGLE_CONTEXT ),
965 $options->get( 'access_users_approved', Helper::NETWORK_CONTEXT )
966 );
967 }
968 return Helper::in_multi_array( $email, $auth_settings_access_users_approved );
969 }
970 }
971 }
972