* @license http://www.gnu.org/licenses/gpl-2.0.html GPL2 * @link http://hawaii.edu/coe/dcdc/wordpress/authorizer/doc/ */ class WP_Plugin_Authorizer { /** * Constructor. */ public function __construct() { // Installation and uninstallation hooks. register_activation_hook( __FILE__, array( $this, 'activate' ) ); register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) ); // Register filters. // Custom wp authentication routine using external service. add_filter( 'authenticate', array( $this, 'custom_authenticate' ), 1, 3 ); // Custom logout action using external service. add_action( 'wp_logout', array( $this, 'custom_logout' ) ); // Removing this bypasses Wordpress authentication (so if external auth fails, // no one can log in); with it enabled, it will run if external auth fails. //remove_filter('authenticate', 'wp_authenticate_username_password', 20, 3); // Create settings link on Plugins page add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'plugin_settings_link' ) ); add_filter( 'network_admin_plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'network_admin_plugin_settings_link' ) ); // Modify login page with a custom password url (if option is set). add_filter( 'lostpassword_url', array( $this, 'custom_lostpassword_url' ) ); // If we have a custom login error, add the filter to show it. $error = get_option( 'auth_settings_advanced_login_error' ); if ( $error && strlen( $error ) > 0 ) { add_filter( 'login_errors', array( $this, 'show_advanced_login_error' ) ); } // Register actions. // Enable localization. Translation files stored in /languages. add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) ); // Perform plugin updates if newer version installed. add_action( 'plugins_loaded', array( $this, 'auth_update_check' ) ); // Update the user meta with this user's failed login attempt. add_action( 'wp_login_failed', array( $this, 'update_login_failed_count' ) ); // Add users who successfully login to the approved list. add_action( 'wp_login', array( $this, 'ensure_wordpress_user_in_approved_list_on_login' ), 10, 2 ); // Create menu item in Settings add_action( 'admin_menu', array( $this, 'add_plugin_page' ) ); // Create options page add_action( 'admin_init', array( $this, 'page_init' ) ); // Update user role in approved list if it's changed in the WordPress edit user page. add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update_role' ) ); add_action( 'personal_options_update', array( $this, 'edit_user_profile_update_role' ) ); // Enqueue javascript and css on the plugin's options page, the // dashboard (for the widget), and the network admin. add_action( 'load-settings_page_authorizer', array( $this, 'load_options_page' ) ); add_action( 'admin_head-index.php', array( $this, 'load_options_page' ) ); add_action( 'load-toplevel_page_authorizer', array( $this, 'load_options_page' ) ); // Add custom css and js to wp-login.php add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts_and_styles' ) ); add_action( 'login_footer', array( $this, 'load_login_footer_js' ) ); // Modify login page with external auth links (if enabled; e.g., google or cas) add_action( 'login_form', array( $this, 'login_form_add_external_service_links' ) ); // Redirect to CAS login when visiting login page (only if option is // enabled, CAS is the only service, and WordPress logins are hidden). add_action( 'login_head', array( $this, 'login_head_maybe_redirect_to_cas' ) ); // Verify current user has access to page they are visiting add_action( 'parse_request', array( $this, 'restrict_access' ), 9 ); // ajax save options from dashboard widget add_action( 'wp_ajax_update_auth_user', array( $this, 'ajax_update_auth_user' ) ); // ajax save options from multisite options page add_action( 'wp_ajax_save_auth_multisite_settings', array( $this, 'ajax_save_auth_multisite_settings' ) ); // ajax save usermeta from options page add_action( 'wp_ajax_update_auth_usermeta', array( $this, 'ajax_update_auth_usermeta' ) ); // ajax verify google login add_action( 'wp_ajax_process_google_login', array( $this, 'ajax_process_google_login' ) ); add_action( 'wp_ajax_nopriv_process_google_login', array( $this, 'ajax_process_google_login' ) ); // Add dashboard widget so instructors can add/edit users with access. // Hint: For Multisite Network Admin Dashboard use wp_network_dashboard_setup instead of wp_dashboard_setup. add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widgets' ) ); // If we have a custom admin message, add the action to show it. $notice = get_option( 'auth_settings_advanced_admin_notice' ); if ( $notice && strlen( $notice ) > 0 ) { add_action( 'admin_notices', array( $this, 'show_advanced_admin_notice' ) ); add_action( 'network_admin_notices', array( $this, 'show_advanced_admin_notice' ) ); } // Load custom javascript for the main site (e.g., for displaying alerts). add_action( 'wp_enqueue_scripts', array( $this, 'auth_public_scripts' ), 20 ); // Multisite-specific actions. if ( is_multisite() ) { // Add network admin options page (global settings for all sites) add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ) ); } // Create login cookie (used by google login) if ( ! isset( $_COOKIE['login_unique'] ) ) { setcookie( 'login_unique', $this->get_cookie_value(), time()+1800, '/', defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : '' ); } // Remove user from authorizer lists when that user is deleted in WordPress. add_action( 'delete_user', array( $this, 'remove_user_from_authorizer_when_deleted' ) ); if ( is_multisite() ) { // Remove multisite user from authorizer lists when that user is deleted from Network Users. add_action( 'remove_user_from_blog', array( $this, 'remove_network_user_from_site_when_removed' ), 10, 2 ); add_action( 'wpmu_delete_user', array( $this, 'remove_network_user_from_authorizer_when_deleted' ) ); } // Add user to authorizer approved list when that user is added to a blog from the Users screen. // Multisite: invite_user action fired when adding (inviting) an existing network user to the current site (with email confirmation). add_action( 'invite_user', array( $this, 'add_existing_user_to_authorizer_when_created' ), 10, 3 ); // Multisite: added_existing_user action fired when adding an existing network user to the current site (without email confirmation). add_action( 'added_existing_user', array( $this, 'add_existing_user_to_authorizer_when_created_noconfirmation' ), 10, 2 ); // Multisite: after_signup_user action fired when adding a new user to the site (with or without email confirmation). add_action( 'after_signup_user', array( $this, 'add_new_user_to_authorizer_when_created' ), 10, 4 ); // Single site: edit_user_created_user action fired when adding a new user to the site (with or without email notification). add_action( 'edit_user_created_user', array( $this, 'add_new_user_to_authorizer_when_created_single_site' ), 10, 2 ); // Add user to network approved users (and remove from individual sites) // when user is elevated to super admin status. add_action( 'grant_super_admin', array( $this, 'grant_super_admin__add_to_network_approved' ) ); // Remove user from network approved users (and add them to the approved // list on sites they are already on) when super admin status is removed. add_action( 'revoke_super_admin', array( $this, 'revoke_super_admin__remove_from_network_approved' ) ); } /** * Plugin activation hook. * Will also activate the plugin for all sites/blogs if this is a "Network enable." * * @return void */ public function activate() { global $wpdb; // If we're in a multisite environment, run the plugin activation for each site when network enabling if ( is_multisite() && isset( $_GET['networkwide'] ) && $_GET['networkwide'] == 1 ) { // Add super admins to the multisite approved list. $auth_multisite_settings_access_users_approved = get_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings_access_users_approved', array() ); $should_update_auth_multisite_settings_access_users_approved = false; foreach ( get_super_admins() as $super_admin ) { $user = get_user_by( 'login', $super_admin ); // Add to approved list if not there. if ( ! $this->in_multi_array( $user->user_email, $auth_multisite_settings_access_users_approved ) ) { $approved_user = array( 'email' => $user->user_email, 'role' => count( $user->roles ) > 0 ? $user->roles[0] : 'administrator', 'date_added' => date( 'M Y', strtotime( $user->user_registered ) ), 'local_user' => true, ); array_push( $auth_multisite_settings_access_users_approved, $approved_user ); $should_update_auth_multisite_settings_access_users_approved = true; } } if ( $should_update_auth_multisite_settings_access_users_approved ) { update_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings_access_users_approved', $auth_multisite_settings_access_users_approved ); } // Run plugin activation on each site in the network. $current_blog_id = $wpdb->blogid; $sites = function_exists( 'get_sites' ) ? get_sites() : wp_get_sites( array( 'limit' => PHP_INT_MAX ) ); foreach ( $sites as $site ) { $blog_id = function_exists( 'get_sites' ) ? $site->blog_id : $site['blog_id']; switch_to_blog( $blog_id ); // Set default plugin options and add current users to approved list. $this->set_default_options(); $this->add_wp_users_to_approved_list(); } switch_to_blog( $current_blog_id ); } else { // Set default plugin options and add current users to approved list. $this->set_default_options(); $this->add_wp_users_to_approved_list(); } } /** * Adds all WordPress users in the current site to the approved list, * unless they are already in the blocked list. Also removes them * from the pending list if they are there. * * Runs in plugin activation hook. * * @return void */ private function add_wp_users_to_approved_list() { // Add current WordPress users to the approved list. $auth_multisite_settings_access_users_approved = is_multisite() ? get_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings_access_users_approved', array() ) : array(); $auth_settings_access_users_pending = $this->get_plugin_option( 'access_users_pending', SINGLE_ADMIN ); $auth_settings_access_users_approved = $this->get_plugin_option( 'access_users_approved', SINGLE_ADMIN ); $auth_settings_access_users_blocked = $this->get_plugin_option( 'access_users_blocked', SINGLE_ADMIN ); $updated = false; foreach ( get_users() as $user ) { // Skip if user is in blocked list. if ( $this->in_multi_array( $user->user_email, $auth_settings_access_users_blocked ) ) { continue; } // Remove from pending list if there. foreach ( $auth_settings_access_users_pending as $key => $pending_user ) { if ( $pending_user['email'] == $user->user_email ) { unset( $auth_settings_access_users_pending[$key] ); $updated = true; } } // Skip if user is in multisite approved list. if ( $this->in_multi_array( $user->user_email, $auth_multisite_settings_access_users_approved ) ) { continue; } // Add to approved list if not there. if ( ! $this->in_multi_array( $user->user_email, $auth_settings_access_users_approved ) ) { $approved_user = array( 'email' => $user->user_email, 'role' => count( $user->roles ) > 0 ? $user->roles[0] : '', 'date_added' => date( 'M Y', strtotime( $user->user_registered ) ), 'local_user' => true, ); array_push( $auth_settings_access_users_approved, $approved_user ); $updated = true; } } if ( $updated ) { update_option( 'auth_settings_access_users_pending', $auth_settings_access_users_pending ); update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved ); } } /** * Plugin deactivation. * * @return void */ public function deactivate() { // Do nothing. } /** * *************************** * External Authentication * *************************** */ /** * Authenticate against an external service. * * @param WP_User $user user to authenticate * @param string $username optional username to authenticate. * @param string $password optional password to authenticate. * * @return WP_User or WP_Error */ public function custom_authenticate( $user, $username, $password ) { // Pass through if already authenticated. if ( is_a( $user, 'WP_User' ) ) { return $user; } else { $user = null; } // If username and password are blank, this isn't a log in attempt $is_login_attempt = strlen( $username ) > 0 && strlen( $password ) > 0; // Check to make sure that $username is not locked out due to too // many invalid login attempts. If it is, tell the user how much // time remains until they can try again. $unauthenticated_user = $is_login_attempt ? get_user_by( 'login', $username ) : false; $unauthenticated_user_is_blocked = false; if ( $is_login_attempt && $unauthenticated_user !== false ) { $last_attempt = get_user_meta( $unauthenticated_user->ID, 'auth_settings_advanced_lockouts_time_last_failed', true ); $num_attempts = get_user_meta( $unauthenticated_user->ID, 'auth_settings_advanced_lockouts_failed_attempts', true ); // Also check the auth_blocked user_meta flag (users in blocked list will get this flag) $unauthenticated_user_is_blocked = get_user_meta( $unauthenticated_user->ID, 'auth_blocked', true ) === 'yes'; } else { $last_attempt = get_option( 'auth_settings_advanced_lockouts_time_last_failed' ); $num_attempts = get_option( 'auth_settings_advanced_lockouts_failed_attempts' ); } // Inactive users should be treated like deleted users (we just // do this to preserve any content they created, but here we should // pretend they don't exist). if ( $unauthenticated_user_is_blocked ) { remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); return new WP_Error( 'empty_password', __( 'ERROR: Incorrect username or password.', 'authorizer' ) ); } // Grab plugin settings. $auth_settings = $this->get_plugin_options( SINGLE_ADMIN, 'allow override' ); // Make sure $last_attempt (time) and $num_attempts are positive integers. // Note: this addresses resetting them if either is unset from above. $last_attempt = abs( intval( $last_attempt ) ); $num_attempts = abs( intval( $num_attempts ) ); // Create semantic lockout variables. $lockouts = $auth_settings['advanced_lockouts']; $time_since_last_fail = time() - $last_attempt; $reset_duration = $lockouts['reset_duration'] * 60; // minutes to seconds $num_attempts_long_lockout = $lockouts['attempts_1'] + $lockouts['attempts_2']; $num_attempts_short_lockout = $lockouts['attempts_1']; $seconds_remaining_long_lockout = $lockouts['duration_2'] * 60 - $time_since_last_fail; $seconds_remaining_short_lockout = $lockouts['duration_1'] * 60 - $time_since_last_fail; // Check if we need to institute a lockout delay if ( $is_login_attempt && $time_since_last_fail > $reset_duration ) { // Enough time has passed since the last invalid attempt and // now that we can reset the failed attempt count, and let this // login attempt go through. $num_attempts = 0; // This does nothing, but include it for semantic meaning. } elseif ( $is_login_attempt && $num_attempts > $num_attempts_long_lockout && $seconds_remaining_long_lockout > 0 ) { // Stronger lockout (1st/2nd round of invalid attempts reached) // Note: set the error code to 'empty_password' so it doesn't // trigger the wp_login_failed hook, which would continue to // increment the failed attempt count. remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); return new WP_Error( 'empty_password', sprintf( __( 'ERROR: There have been too many invalid login attempts for the username %1$s. Please wait %3$s before trying again. Lost your password?', 'authorizer' ), $username, $seconds_remaining_long_lockout, $this->seconds_as_sentence( $seconds_remaining_long_lockout ), wp_lostpassword_url() ) ); } elseif ( $is_login_attempt && $num_attempts > $num_attempts_short_lockout && $seconds_remaining_short_lockout > 0 ) { // Normal lockout (1st round of invalid attempts reached) // Note: set the error code to 'empty_password' so it doesn't // trigger the wp_login_failed hook, which would continue to // increment the failed attempt count. remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); return new WP_Error( 'empty_password', sprintf( __( 'ERROR: There have been too many invalid login attempts for the username %1$s. Please wait %3$s before trying again. Lost your password?', 'authorizer' ), $username, $seconds_remaining_short_lockout, $this->seconds_as_sentence( $seconds_remaining_short_lockout ), wp_lostpassword_url() ) ); } // Start external authentication. $externally_authenticated_emails = array(); $authenticated_by = ''; $result = null; // Try Google authentication if it's enabled and we don't have a // successful login yet. if ( $auth_settings['google'] === '1' && count( $externally_authenticated_emails ) === 0 && ! is_wp_error( $result ) ) { $result = $this->custom_authenticate_google( $auth_settings ); if ( ! is_null( $result ) && ! is_wp_error( $result ) ) { if ( is_array( $result['email'] ) ) { $externally_authenticated_emails = $result['email']; } else { $externally_authenticated_emails[] = $result['email']; } $authenticated_by = $result['authenticated_by']; } } // Try CAS authentication if it's enabled and we don't have a // successful login yet. if ( $auth_settings['cas'] === '1' && count( $externally_authenticated_emails ) === 0 && ! is_wp_error( $result ) ) { $result = $this->custom_authenticate_cas( $auth_settings ); if ( ! is_null( $result ) && ! is_wp_error( $result ) ) { if ( is_array( $result['email'] ) ) { $externally_authenticated_emails = $result['email']; } else { $externally_authenticated_emails[] = $result['email']; } $authenticated_by = $result['authenticated_by']; } } // Try LDAP authentication if it's enabled and we don't have an // authenticated user yet. if ( $auth_settings['ldap'] === '1' && count( $externally_authenticated_emails ) === 0 && ! is_wp_error( $result ) ) { $result = $this->custom_authenticate_ldap( $auth_settings, $username, $password ); if ( ! is_null( $result ) && ! is_wp_error( $result ) ) { if ( is_array( $result['email'] ) ) { $externally_authenticated_emails = $result['email']; } else { $externally_authenticated_emails[] = $result['email']; } $authenticated_by = $result['authenticated_by']; } } // Skip to WordPress authentication if we don't have an externally // authenticated user. if ( count( array_filter( $externally_authenticated_emails ) ) < 1 ) { return $result; } // Remove duplicate and blank emails, if any. $externally_authenticated_emails = array_filter( array_unique( $externally_authenticated_emails ) ); // If we've made it this far, we should have an externally // authenticated user. The following should be set: // $externally_authenticated_emails // $authenticated_by // Get the external user's WordPress account by email address. foreach ( $externally_authenticated_emails as $externally_authenticated_email ) { $user = get_user_by( 'email', $externally_authenticated_email ); // If we've already found a WordPress user associated with one // of the supplied email addresses, don't keep examining other // email addresses associated with the externally authenticated user. if ( $user !== FALSE ) { break; } } // Check this external user's access against the access lists // (pending, approved, blocked) $result = $this->check_user_access( $user, $externally_authenticated_emails, $result ); // Fail with message if there was an error creating/adding the user. if ( is_wp_error( $result ) || $result === 0 ) { return $result; } // If we created a new user in check_user_access(), log that user in. if ( get_class( $result ) === 'WP_User' ) { $user = $result; } // We'll track how this user was authenticated in user meta. if ( $user ) { update_user_meta( $user->ID, 'authenticated_by', $authenticated_by ); } // If we haven't exited yet, we have a valid/approved user, so authenticate them. return $user; } /** * This function will fail with a wp_die() message to the user if they * don't have access. * * @param WP_User $user User to check * @param [type] $user_emails Array of user's plaintext emails (in case current user doesn't have a WP account) * @param [type] $user_data Array of keys for email, username, first_name, last_name, * authenticated_by, google_attributes, cas_attributes, ldap_attributes. * @return WP_Error if there was an error on user creation / adding user to blog * wp_die() if user does not have access * null if user has access (success) * WP_User if user has access and a new account was created for them */ private function check_user_access( $user, $user_emails, $user_data = array() ) { // Grab plugin settings. $auth_settings = $this->get_plugin_options( SINGLE_ADMIN, 'allow override' ); $auth_settings_access_users_pending = $this->sanitize_user_list( $this->get_plugin_option( 'access_users_pending', SINGLE_ADMIN ) ); $auth_settings_access_users_approved = $this->sanitize_user_list( array_merge( $this->get_plugin_option( 'access_users_approved', SINGLE_ADMIN ), $this->get_plugin_option( 'access_users_approved', MULTISITE_ADMIN ) ) ); /** * Filter whether to block the currently logging in user based on any of * their user attributes. * * @param bool $user_is_blocked Whether to block the currently logging in user. * @param array $user_data User data returned from external service. */ $allow_login = apply_filters( 'authorizer_allow_login', true, $user_data ); // Check our externally authenticated user against the block list. // If any of their email addresses are blocked, set the relevant user // meta field, and show them an error screen. foreach ( $user_emails as $user_email ) { if ( ! $allow_login || $this->is_email_in_list( $user_email, 'blocked' ) ) { // Add user to blocked list if it was blocked via the filter. if ( ! $allow_login && ! $this->is_email_in_list( $user_email, 'blocked' ) ) { $auth_settings_access_users_blocked = $this->sanitize_user_list( $this->get_plugin_option( 'access_users_blocked', SINGLE_ADMIN ) ); array_push( $auth_settings_access_users_blocked, array( 'email' => $user_email, 'date_added' => date( 'M Y' ), )); update_option( 'auth_settings_access_users_blocked', $auth_settings_access_users_blocked ); } // If the blocked external user has a WordPress account, mark it as // blocked (enforce block in this->authenticate()). if ( $user ) { update_user_meta( $user->ID, 'auth_blocked', 'yes' ); } // Notify user about blocked status and return without authenticating them. $redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : home_url(); $page_title = sprintf( /* TRANSLATORS: %s: Name of blog */ __( '%s - Access Restricted', 'authorizer' ), get_bloginfo( 'name' ) ); $error_message = apply_filters( 'the_content', $auth_settings['access_blocked_redirect_to_message'] ) . '
' . '' . __( 'Back', 'authorizer' ) . '
'; update_option( 'auth_settings_advanced_login_error', $error_message ); wp_die( $error_message, $page_title ); } } // Get the default role for this new user. $default_role = $user && is_array( $user->roles ) && count( $user->roles ) > 0 ? $user->roles[0] : $auth_settings['access_default_role']; /** * Filter the role of the user currently logging in. The role will be * set to the default (specified in Authorizer options) for new users, * or the user's current role for existing users. This filter allows * changing user roles based on custom CAS/LDAP attributes. * @param bool $role Role of the user currently logging in. * @param array $user_data User data returned from external service. */ $approved_role = apply_filters( 'authorizer_custom_role', $default_role, $user_data ); // Iterate through each of the email addresses provided by the external // service and determine if any of them have access. $last_email = end( $user_emails ); reset( $user_emails ); foreach ( $user_emails as $user_email ) { $is_newly_approved_user = false; // If this externally authenticated user is an existing administrator // (administrator in single site mode, or super admin in network mode), // and is not in the blocked list, let them in. if ( $user && is_super_admin( $user->ID ) ) { return; } // If this externally authenticated user isn't in the approved list // and login access is set to "All authenticated users," add them // to the approved list (they'll get an account created below if // they don't have one yet). if ( ! $this->is_email_in_list( $user_email, 'approved' ) && $auth_settings['access_who_can_login'] === 'external_users' ) { $is_newly_approved_user = true; // If this user happens to be in the pending list (rare), // remove them from pending before adding them to approved. if ( $this->is_email_in_list( $user_email, 'pending' ) ) { foreach ( $auth_settings_access_users_pending as $key => $pending_user ) { if ( $pending_user['email'] === $user_email ) { unset( $auth_settings_access_users_pending[ $key ] ); update_option( 'auth_settings_access_users_pending', $auth_settings_access_users_pending ); break; } } } // Add this user to the approved list. $approved_user = array( 'email' => $user_email, 'role' => $approved_role, 'date_added' => date( "Y-m-d H:i:s" ), ); array_push( $auth_settings_access_users_approved, $approved_user ); update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved ); } // Check our externally authenticated user against the approved // list. If they are approved, log them in (and create their account // if necessary). if ( $is_newly_approved_user || $this->is_email_in_list( $user_email, 'approved' ) ) { $user_info = $is_newly_approved_user ? $approved_user : $this->get_user_info_from_list( $user_email, $auth_settings_access_users_approved ); // If this user's role was modified above (in the // authorizer_custom_role filter), use that value instead of // whatever is specified in the approved list. if ( $default_role !== $approved_role ) { $user_info['role'] = $approved_role; } // If the approved external user does not have a WordPress account, create it if ( ! $user ) { // If there's already a user with this username (e.g., // johndoe/johndoe@gmail.com exists, and we're trying to add // johndoe/johndoe@example.com), use the full email address // as the username. if ( array_key_exists( 'username', $user_data ) ) { $username = $user_data['username']; } else { $username = explode( '@', $user_info['email'] ); $username = $username[0]; } if ( get_user_by( 'login', $username ) !== false ) { $username = $user_info['email']; } $result = wp_insert_user( array( 'user_login' => strtolower( $username ), 'user_pass' => wp_generate_password(), // random password 'first_name' => array_key_exists( 'first_name', $user_data ) ? $user_data['first_name'] : '', 'last_name' => array_key_exists( 'last_name', $user_data ) ? $user_data['last_name'] : '', 'user_email' => strtolower( $user_info['email'] ), 'user_registered' => date( 'Y-m-d H:i:s' ), 'role' => $user_info['role'], ) ); // Fail with message if error. if ( is_wp_error( $result ) || $result === 0 ) { return $result; } // Authenticate as new user $user = new WP_User( $result ); // If multisite, iterate through all sites in the network and add the user // currently logging in to any of them that have the user on the approved list. // Note: this is useful for first-time logins--some users will have access // to multiple sites, and this prevents them from having to log into each // site individually to get access. if ( is_multisite() ) { $site_ids_of_user = array_map( function ( $site_of_user ) { return $site_of_user->userblog_id; }, get_blogs_of_user( $user->ID ) ); $sites = function_exists( 'get_sites' ) ? get_sites() : wp_get_sites( array( 'limit' => PHP_INT_MAX ) ); foreach ( $sites as $site ) { $blog_id = function_exists( 'get_sites' ) ? $site->blog_id : $site['blog_id']; // Skip if user is already added to this site. if ( in_array( $blog_id, $site_ids_of_user ) ) { continue; } // Check if user is on the approved list of this site they are not added to. $other_auth_settings_access_users_approved = get_blog_option( $blog_id, 'auth_settings_access_users_approved', array() ); if ( $this->in_multi_array( $user->user_email, $other_auth_settings_access_users_approved ) ) { $other_user_info = $this->get_user_info_from_list( $user->user_email, $other_auth_settings_access_users_approved ); // Add user to other site. add_user_to_blog( $blog_id, $user->ID, $other_user_info['role'] ); } } } // Check if this new user has any preassigned usermeta // values in their approved list entry, and apply them to // their new WordPress account. if ( array_key_exists( 'usermeta', $user_info ) && is_array( $user_info['usermeta'] ) ) { $meta_key = $this->get_plugin_option( 'advanced_usermeta' ); if ( array_key_exists( 'meta_key', $user_info['usermeta'] ) && array_key_exists( 'meta_value', $user_info['usermeta'] ) ) { // Only update the usermeta if the stored value matches // the option set in authorizer settings (if they don't // match it's probably old data). if ( $meta_key === $user_info['usermeta']['meta_key'] ) { // Update user's usermeta value for usermeta key stored in authorizer options. if ( strpos( $meta_key, 'acf___' ) === 0 && class_exists( 'acf' ) ) { // We have an ACF field value, so use the ACF function to update it. update_field( str_replace('acf___', '', $meta_key ), $user_info['usermeta']['meta_value'], 'user_' . $user->ID ); } else { // We have a normal usermeta value, so just update it via the WordPress function. update_user_meta( $user->ID, $meta_key, $user_info['usermeta']['meta_value'] ); } } } elseif ( is_multisite() && count( $user_info['usermeta'] ) > 0 ) { // Update usermeta for each multisite blog defined for this user. foreach ( $user_info['usermeta'] as $blog_id => $usermeta ) { if ( array_key_exists( 'meta_key', $usermeta ) && array_key_exists( 'meta_value', $usermeta ) ) { // 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). if ( ! is_user_member_of_blog( $user->ID, $blog_id ) ) { add_user_to_blog( $blog_id, $user->ID, $user_info['role'] ); } switch_to_blog( $blog_id ); // Update user's usermeta value for usermeta key stored in authorizer options. if ( strpos( $meta_key, 'acf___' ) === 0 && class_exists( 'acf' ) ) { // We have an ACF field value, so use the ACF function to update it. update_field( str_replace('acf___', '', $meta_key ), $usermeta['meta_value'], 'user_' . $user->ID ); } else { // We have a normal usermeta value, so just update it via the WordPress function. update_user_meta( $user->ID, $meta_key, $usermeta['meta_value'] ); } restore_current_blog(); } } } } } else { // Update first/last names of WordPress user from external // service if that option is set. if ( ( array_key_exists( 'authenticated_by', $user_data ) && $user_data['authenticated_by'] === 'cas' && array_key_exists( 'cas_attr_update_on_login', $auth_settings ) && $auth_settings['cas_attr_update_on_login'] == 1 ) || ( array_key_exists( 'authenticated_by', $user_data ) && $user_data['authenticated_by'] === 'ldap' && array_key_exists( 'ldap_attr_update_on_login', $auth_settings ) && $auth_settings['ldap_attr_update_on_login'] == 1 ) ) { if ( array_key_exists( 'first_name', $user_data ) && strlen( $user_data['first_name'] ) > 0 ) { wp_update_user( array( 'ID' => $user->ID, 'first_name' => $user_data['first_name'], )); } if ( array_key_exists( 'last_name', $user_data ) && strlen( $user_data['last_name'] ) > 0 ) { wp_update_user( array( 'ID' => $user->ID, 'last_name' => $user_data['last_name'], )); } } // Update this user's role if it was modified in the // authorizer_custom_role filter. if ( $default_role !== $approved_role ) { wp_update_user( array( 'ID' => $user->ID, 'role' => $approved_role, )); } } // If this is multisite, add new user to current blog. if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) { $result = add_user_to_blog( get_current_blog_id(), $user->ID, $user_info['role'] ); // Fail with message if error. if ( is_wp_error( $result ) ) { return $result; } } // Ensure user has the same role as their entry in the approved list. // (This is just a precaution, the role should already be set when // saving admin options in the sanitizing function.) if ( $user_info && ! array_key_exists( $user_info['role'], $user->roles ) ) { $user->set_role( $user_info['role'] ); } return $user; // Note: only do this for the last email address we are checking (we need // to iterate through them all to make sure one of them isn't approved). } elseif ( $user_email === $last_email ) { // User isn't an admin, is not blocked, and is not approved. // Add them to the pending list and notify them and their instructor. if ( strlen( $user_email ) > 0 && ! $this->is_email_in_list( $user_email, 'pending' ) ) { $pending_user = array(); $pending_user['email'] = $user_email; $pending_user['role'] = $approved_role; $pending_user['date_added'] = ''; array_push( $auth_settings_access_users_pending, $pending_user ); update_option( 'auth_settings_access_users_pending', $auth_settings_access_users_pending ); // Create strings used in the email notification. $site_name = get_bloginfo( 'name' ); $site_url = get_bloginfo( 'url' ); $authorizer_options_url = $auth_settings['advanced_admin_menu'] === 'settings' ? admin_url( 'options-general.php?page=authorizer' ) : admin_url( '?page=authorizer' ); // Notify users with the role specified in "Which role should // receive email notifications about pending users?". if ( strlen( $auth_settings['access_role_receive_pending_emails'] ) > 0 ) { foreach ( get_users( array( 'role' => $auth_settings['access_role_receive_pending_emails'] ) ) as $user_recipient ) { wp_mail( $user_recipient->user_email, sprintf( /* TRANSLATORS: 1: User email 2: Name of site */ __( 'Action required: Pending user %1$s at %2$s', 'authorizer' ), $pending_user['email'], $site_name ), sprintf( /* TRANSLATORS: 1: Name of site 2: URL of site 3: URL of authorizer */ __( "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' ), $site_name, $site_url, $authorizer_options_url ) ); } } } // Notify user about pending status and return without authenticating them. $redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : home_url(); $page_title = get_bloginfo( 'name' ) . ' - Access Pending'; $error_message = apply_filters( 'the_content', $auth_settings['access_pending_redirect_to_message'] ) . '' . '' . __( 'Back', 'authorizer' ) . '
'; update_option( 'auth_settings_advanced_login_error', $error_message ); wp_die( $error_message, $page_title ); } } // Sanity check: if we made it here without returning, something has gone wrong. return new WP_Error( 'invalid_login', __( 'Invalid login attempted.', 'authorizer' ) ); } /** * Verify the Google login and set a session token. * * Flow: "Sign in with Google" button clicked; JS Google library * called; JS function signInCallback() fired with results from Google; * signInCallback() posts code and nonce (via AJAX) to this function; * This function checks the token using the Google PHP library, and * saves it to a session variable if it's authentic; control passes * back to signInCallback(), which will reload the current page * (wp-login.php) on success; wp-login.php reloads; custom_authenticate * hooked into authenticate action fires again, and * custom_authenticate_google() runs to verify the token; once verified * custom_authenticate proceeds as normal with the google email address * as a successfully authenticated external user. * * @return void, but die with the value to return to the success() function in AJAX call signInCallback() */ function ajax_process_google_login() { $nonce = array_key_exists( 'nonce', $_POST ) ? $_POST['nonce'] : ''; $code = array_key_exists( 'code', $_POST ) ? $_POST['code'] : null; // Nonce check. if ( ! wp_verify_nonce( $nonce, 'google_csrf_nonce' ) ) { return ''; } // Grab plugin settings. $auth_settings = $this->get_plugin_options( SINGLE_ADMIN, 'allow override' ); // Add Google API PHP Client. // @see https://github.com/google/google-api-php-client branch:v1-master require_once dirname( __FILE__ ) . '/vendor/google-api-php-client/src/Google/autoload.php'; // Build the Google Client. $client = new Google_Client(); $client->setApplicationName( 'WordPress' ); $client->setClientId( $auth_settings['google_clientid'] ); $client->setClientSecret( $auth_settings['google_clientsecret'] ); $client->setRedirectUri( 'postmessage' ); // If the hosted domain parameter is set, restrict logins to that domain. if ( array_key_exists( 'google_hosteddomain', $auth_settings ) && strlen( $auth_settings['google_hosteddomain'] ) > 0 ) { $client->setHostedDomain( $auth_settings['google_hosteddomain'] ); } // Get one time use token (if it doesn't exist, we'll create one below) session_start(); $token = array_key_exists( 'token', $_SESSION ) ? json_decode( $_SESSION['token'] ) : null; if ( empty( $token ) ) { // Exchange the OAuth 2.0 authorization code for user credentials. $client->authenticate( $code ); $token = json_decode( $client->getAccessToken() ); // Store the token in the session for later use. $_SESSION['token'] = json_encode( $token ); $response = "Successfully authenticated."; } else { $client->setAccessToken( json_encode( $token ) ); $response = 'Already authenticated.'; } die( $response ); } /** * Validate this user's credentials against Google. * * @param array $auth_settings Plugin settings * @return [mixed] Array containing email, authenticated_by, * first_name, last_name, and username * strings for the successfully authenticated * user, or WP_Error() object on failure, * or null if not attempting a google login. */ private function custom_authenticate_google( $auth_settings ) { // Move on if Google auth hasn't been requested here. if ( empty( $_GET['external'] ) || $_GET['external'] !== 'google' ) { return null; } // Get one time use token session_start(); $token = array_key_exists( 'token', $_SESSION ) ? json_decode( $_SESSION['token'] ) : null; // No token, so this is not a succesful Google login. if ( is_null( $token ) ) { return null; } // Add Google API PHP Client. // @see https://github.com/google/google-api-php-client branch:v1-master require_once dirname( __FILE__ ) . '/vendor/google-api-php-client/src/Google/autoload.php'; // Build the Google Client. $client = new Google_Client(); $client->setApplicationName( 'WordPress' ); $client->setClientId( $auth_settings['google_clientid'] ); $client->setClientSecret( $auth_settings['google_clientsecret'] ); $client->setRedirectUri( 'postmessage' ); // If the hosted domain parameter is set, restrict logins to that domain. if ( array_key_exists( 'google_hosteddomain', $auth_settings ) && strlen( $auth_settings['google_hosteddomain'] ) > 0 ) { $client->setHostedDomain( $auth_settings['google_hosteddomain'] ); } // Verify this is a successful Google authentication try { $ticket = $client->verifyIdToken( $token->id_token, $auth_settings['google_clientid'] ); } catch ( Google_Auth_Exception $e ) { // Invalid ticket, so this in not a successful Google login. return new WP_Error( 'invalid_google_login', __( 'Invalid Google credentials provided.', 'authorizer' ) ); } // Invalid ticket, so this in not a successful Google login. if ( ! $ticket ) { return new WP_Error( 'invalid_google_login', __( 'Invalid Google credentials provided.', 'authorizer' ) ); } // Get email address $attributes = $ticket->getAttributes(); $email = $attributes['payload']['email']; $email_domain = substr( strrchr( $email, '@' ), 1 ); $username = current( explode( '@', $email ) ); // Fail if hd param is set and the logging in user's email address doesn't // match the allowed hosted domain. // See: https://developers.google.com/identity/protocols/OpenIDConnect#hd-param // See: https://github.com/google/google-api-php-client/blob/v1-master/src/Google/Client.php#L407-L416 // Note: Will have to upgrade to google-api-php-client v2 or higher for // this to function server-side; it's not complete in v1, so this check // is only performed here. if ( array_key_exists( 'google_hosteddomain', $auth_settings ) && strlen( $auth_settings['google_hosteddomain'] ) > 0 && $email_domain !== $auth_settings['google_hosteddomain'] ) { $this->custom_logout(); return new WP_Error( 'invalid_google_login', __( 'Google credentials do not match the allowed hosted domain', 'authorizer' ) . ' (' . $auth_settings['google_hosteddomain'] . ').' ); } return array( 'email' => $email, 'username' => $username, 'first_name' => '', 'last_name' => '', 'authenticated_by' => 'google', 'google_attributes' => $attributes, ); } /** * Validate this user's credentials against CAS. * * @param array $auth_settings Plugin settings * @return [mixed] Array containing 'email' and 'authenticated_by' * strings for the successfully authenticated * user, or WP_Error() object on failure, * or null if not attempting a CAS login. */ private function custom_authenticate_cas( $auth_settings ) { // Move on if CAS hasn't been requested here. if ( empty( $_GET['external'] ) || $_GET['external'] !== 'cas' ) { return null; } // Get the CAS server version (default to SAML_VERSION_1_1). // See: https://developer.jasig.org/cas-clients/php/1.3.4/docs/api/group__public.html $cas_version = SAML_VERSION_1_1; if ( $auth_settings['cas_version'] === 'CAS_VERSION_3_0' ) { $cas_version = CAS_VERSION_3_0; } elseif ( $auth_settings['cas_version'] === 'CAS_VERSION_2_0' ) { $cas_version = CAS_VERSION_2_0; } elseif ( $auth_settings['cas_version'] === 'CAS_VERSION_1_0' ) { $cas_version = CAS_VERSION_1_0; } // Set the CAS client configuration phpCAS::client( $cas_version, $auth_settings['cas_host'], intval( $auth_settings['cas_port'] ), $auth_settings['cas_path'] ); // Update server certificate bundle if it doesn't exist or is older // than 6 months, then use it to ensure CAS server is legitimate. // Note: only try to update if the system has the php_openssl extension. $cacert_url = 'https://curl.haxx.se/ca/cacert.pem'; $cacert_path = plugin_dir_path( __FILE__ ) . 'vendor/cacert.pem'; $time_180_days = 180 * 24 * 60 * 60; // days * hours * minutes * seconds $time_180_days_ago = time() - $time_180_days; if ( extension_loaded( 'openssl' ) && ( ! file_exists( $cacert_path ) || filemtime( $cacert_path ) < $time_180_days_ago ) ) { // Get new cacert.pem file from https://curl.haxx.se/ca/cacert.pem. $response = wp_safe_remote_get( $cacert_url ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) || ! array_key_exists( 'body', $response ) ) { new WP_Error( 'cannot_update_cacert', __( 'Unable to update outdated server certificates from https://curl.haxx.se/ca/cacert.pem.', 'authorizer' ) ); } $cacert_contents = $response['body']; // Write out the updated certs to the plugin directory. file_put_contents( $cacert_path, $cacert_contents ); } phpCAS::setCasServerCACert( $cacert_path ); // Authenticate against CAS try { phpCAS::forceAuthentication(); } catch ( CAS_AuthenticationException $e ) { // CAS server threw an error in isAuthenticated(), potentially because // the cached ticket is outdated. Try renewing the authentication. try { phpCAS::renewAuthentication(); } catch ( CAS_AuthenticationException $e ) { error_log( __( 'CAS server returned an Authentication Exception. Details:', 'authorizer' ) ); error_log( print_r( $e, true ) ); // CAS server is throwing errors on this login, so try logging the // user out of CAS and redirecting them to the login page. phpCAS::logoutWithRedirectService( wp_login_url() ); die(); } } // Get username (as specified by the CAS server). $username = phpCAS::getUser(); // Get email that successfully authenticated against the external service (CAS). $externally_authenticated_email = strtolower( $username ); if ( ! filter_var( $externally_authenticated_email, FILTER_VALIDATE_EMAIL ) ) { // If we can't get the user's email address from a CAS attribute, // try to guess the domain from the CAS server hostname. This will only // be used if we can't discover the email address from CAS attributes. $domain_guess = preg_match( '/[^.]*\.[^.]*$/', $auth_settings['cas_host'], $matches ) === 1 ? $matches[0] : ''; $externally_authenticated_email = strtolower( $username ) . '@' . $domain_guess; } // Retrieve the user attributes (e.g., email address, first name, last name) from the CAS server. $cas_attributes = phpCAS::getAttributes(); // Get user email if it is specified in another field. if ( array_key_exists( 'cas_attr_email', $auth_settings ) && strlen( $auth_settings['cas_attr_email'] ) > 0 ) { // If the email attribute starts with an at symbol (@), assume that the // email domain is manually entered there (instead of a reference to a // CAS attribute), and combine that with the username to create the email. // Otherwise, look up the CAS attribute for email. if ( substr( $auth_settings['cas_attr_email'], 0, 1 ) === '@' ) { $externally_authenticated_email = strtolower( $username . $auth_settings['cas_attr_email'] ); } elseif ( // If a CAS attribute has been specified as containing the email address, use that instead. // Email attribute can be a string or an array of strings. array_key_exists( $auth_settings['cas_attr_email'], $cas_attributes ) && ( ( is_array( $cas_attributes[$auth_settings['cas_attr_email']] ) && count( $cas_attributes[$auth_settings['cas_attr_email']] ) > 0 ) || ( is_string( $cas_attributes[$auth_settings['cas_attr_email']] ) && strlen( $cas_attributes[$auth_settings['cas_attr_email']] ) > 0 ) ) ) { $externally_authenticated_email = $cas_attributes[$auth_settings['cas_attr_email']]; } } // Get user first name and last name. $first_name = array_key_exists( 'cas_attr_first_name', $auth_settings ) && strlen( $auth_settings['cas_attr_first_name'] ) > 0 && array_key_exists( $auth_settings['cas_attr_first_name'], $cas_attributes ) && strlen( $cas_attributes[$auth_settings['cas_attr_first_name']] ) > 0 ? $cas_attributes[$auth_settings['cas_attr_first_name']] : ''; $last_name = array_key_exists( 'cas_attr_last_name', $auth_settings ) && strlen( $auth_settings['cas_attr_last_name'] ) > 0 && array_key_exists( $auth_settings['cas_attr_last_name'], $cas_attributes ) && strlen( $cas_attributes[$auth_settings['cas_attr_last_name']] ) > 0 ? $cas_attributes[$auth_settings['cas_attr_last_name']] : ''; return array( 'email' => $externally_authenticated_email, 'username' => $username, 'first_name' => $first_name, 'last_name' => $last_name, 'authenticated_by' => 'cas', 'cas_attributes' => $cas_attributes, ); } /** * Validate this user's credentials against LDAP. * * @param array $auth_settings Plugin settings * @param string $username Attempted username from authenticate action * @param string $password Attempted password from authenticate action * @return [mixed] Array containing 'email' and 'authenticated_by' * strings for the successfully authenticated * user, or WP_Error() object on failure, * or null if skipping LDAP auth and falling back to WP auth. */ private function custom_authenticate_ldap( $auth_settings, $username, $password ) { // Get the FQDN from the LDAP search base domain components (dc). For // example, ou=people,dc=example,dc=edu,dc=uk would yield user@example.edu.uk $search_base_components = explode( ',', trim( $auth_settings['ldap_search_base'] ) ); $domain = array(); foreach ( $search_base_components as $search_base_component ) { $component = explode( '=', $search_base_component ); if ( count( $component ) === 2 && $component[0] === 'dc' ) { $domain[] = $component[1]; } } $domain = implode( '.', $domain ); // If we can't get the logging in user's email address from an LDAP attribute, // just use the domain from the LDAP host. This will only be used if we // can't discover the email address from an LDAP attribute. if ( empty( $domain ) ) { $domain = preg_match( '/[^.]*\.[^.]*$/', $auth_settings['ldap_host'], $matches ) === 1 ? $matches[0] : ''; } // remove @domain if it exists in the username (i.e., if user entered their email) $username = str_replace( '@' . $domain, '', $username ); // Fail silently (fall back to WordPress authentication) if both username // and password are empty (this will be the case when visiting wp-login.php // for the first time, or when clicking the Log In button without filling // out either field. if ( empty( $username ) && empty( $password ) ) { return null; } // Fail with error message if username or password is blank. if ( empty( $username ) ) { return new WP_Error( 'empty_username', __( 'You must provide a username or email.', 'authorizer' ) ); } if ( empty( $password ) ) { return new WP_Error( 'empty_password', __( 'You must provide a password.', 'authorizer' ) ); } // If php5-ldap extension isn't installed on server, fall back to WP auth. if ( ! function_exists( 'ldap_connect' ) ) { return null; } // Authenticate against LDAP using options provided in plugin settings. $result = false; $ldap_user_dn = ''; $first_name = ''; $last_name = ''; $email = ''; // Construct LDAP connection parameters. ldap_connect() takes either a // hostname or a full LDAP URI as its first parameter (works with OpenLDAP // 2.x.x or later). If it's an LDAP URI, the second parameter, $port, is // ignored, and port must be specified in the full URI. An LDAP URI is of // the form ldap://hostname:port or ldaps://hostname:port. $ldap_host = $auth_settings['ldap_host']; $ldap_port = intval( $auth_settings['ldap_port'] ); $parsed_host = parse_url( $ldap_host ); // Fail (fall back to WordPress auth) if invalid host is specified. if ( $parsed_host === false ) { return null; } // If a scheme is in the LDAP host, use full LDAP URI instead of just hostname. if ( array_key_exists( 'scheme', $parsed_host ) ) { // If the port isn't in the LDAP URI, use the one in the LDAP port field. if ( ! array_key_exists( 'port', $parsed_host ) ) { $parsed_host['port'] = $ldap_port; } $ldap_host = $this->build_url( $parsed_host ); } // Establish LDAP connection. $ldap = ldap_connect( $ldap_host, $ldap_port ); ldap_set_option( $ldap, LDAP_OPT_PROTOCOL_VERSION, 3 ); if ( $auth_settings['ldap_tls'] == 1 ) { if( ! ldap_start_tls( $ldap ) ) { return null; } } // Set bind credentials; attempt an anonymous bind if not provided. $bind_rdn = NULL; $bind_password = NULL; if ( strlen( $auth_settings['ldap_user'] ) > 0 ) { $bind_rdn = $auth_settings['ldap_user']; $bind_password = $this->decrypt( base64_decode( $auth_settings['ldap_password'] ) ); } // Attempt LDAP bind. $result = @ldap_bind( $ldap, $bind_rdn, $bind_password ); if ( ! $result ) { // Can't connect to LDAP, so fall back to WordPress authentication. return null; } // Look up the bind DN (and first/last name) of the user trying to // log in by performing an LDAP search for the login username in // the field specified in the LDAP settings. This setup is common. $ldap_attributes_to_retrieve = array( 'dn' ); if ( array_key_exists( 'ldap_attr_first_name', $auth_settings ) && strlen( $auth_settings['ldap_attr_first_name'] ) > 0 ) { array_push( $ldap_attributes_to_retrieve, $auth_settings['ldap_attr_first_name'] ); } if ( array_key_exists( 'ldap_attr_last_name', $auth_settings ) && strlen( $auth_settings['ldap_attr_last_name'] ) > 0 ) { array_push( $ldap_attributes_to_retrieve, $auth_settings['ldap_attr_last_name'] ); } if ( array_key_exists( 'ldap_attr_email', $auth_settings ) && strlen( $auth_settings['ldap_attr_email'] ) > 0 && substr( $auth_settings['ldap_attr_email'], 0, 1 ) !== '@' ) { array_push( $ldap_attributes_to_retrieve, $auth_settings['ldap_attr_email'] ); } $ldap_search = ldap_search( $ldap, $auth_settings['ldap_search_base'], "(" . $auth_settings['ldap_uid'] . "=" . $username . ")", $ldap_attributes_to_retrieve ); $ldap_entries = ldap_get_entries( $ldap, $ldap_search ); // If we didn't find any users in ldap, fall back to WordPress authentication. if ( $ldap_entries['count'] < 1 ) { return null; } // Get the bind dn and first/last names; if there are multiple results returned, just get the last one. for ( $i = 0; $i < $ldap_entries['count']; $i++ ) { $ldap_user_dn = $ldap_entries[$i]['dn']; // Get user first name and last name. if ( array_key_exists( 'ldap_attr_first_name', $auth_settings ) && strlen( $auth_settings['ldap_attr_first_name'] ) > 0 && array_key_exists( $auth_settings['ldap_attr_first_name'], $ldap_entries[$i] ) && $ldap_entries[$i][$auth_settings['ldap_attr_first_name']]['count'] > 0 && strlen( $ldap_entries[$i][$auth_settings['ldap_attr_first_name']][0] ) > 0 ) { $first_name = $ldap_entries[$i][$auth_settings['ldap_attr_first_name']][0]; } if ( array_key_exists( 'ldap_attr_last_name', $auth_settings ) && strlen( $auth_settings['ldap_attr_last_name'] ) > 0 && array_key_exists( $auth_settings['ldap_attr_last_name'], $ldap_entries[$i] ) && $ldap_entries[$i][$auth_settings['ldap_attr_last_name']]['count'] > 0 && strlen( $ldap_entries[$i][$auth_settings['ldap_attr_last_name']][0] ) > 0 ) { $last_name = $ldap_entries[$i][$auth_settings['ldap_attr_last_name']][0]; } // Get user email if it is specified in another field. if ( array_key_exists( 'ldap_attr_email', $auth_settings ) && strlen( $auth_settings['ldap_attr_email'] ) > 0 ) { // If the email attribute starts with an at symbol (@), assume that the // email domain is manually entered there (instead of a reference to an // LDAP attribute), and combine that with the username to create the email. // Otherwise, look up the LDAP attribute for email. if ( substr( $auth_settings['ldap_attr_email'], 0, 1 ) === '@' ) { $email = strtolower( $username . $auth_settings['ldap_attr_email'] ); } elseif ( array_key_exists( $auth_settings['ldap_attr_email'], $ldap_entries[$i] ) && $ldap_entries[$i][$auth_settings['ldap_attr_email']]['count'] > 0 && strlen( $ldap_entries[$i][$auth_settings['ldap_attr_email']][0] ) > 0 ) { $email = strtolower( $ldap_entries[$i][$auth_settings['ldap_attr_email']][0] ); } } } $result = @ldap_bind( $ldap, $ldap_user_dn, $password ); if ( ! $result ) { // We have a real ldap user, but an invalid password. Pass // through to wp authentication after failing LDAP (since // this could be a local account that happens to be the // same name as an LDAP user). return null; } // User successfully authenticated against LDAP, so set the relevant variables. $externally_authenticated_email = $username . '@' . $domain; // If an LDAP attribute has been specified as containing the email address, use that instead. if ( strlen( $email ) > 0 ) { $externally_authenticated_email = $email; } return array( 'email' => $externally_authenticated_email, 'username' => $username, 'first_name' => $first_name, 'last_name' => $last_name, 'authenticated_by' => 'ldap', 'ldap_attributes' => $ldap_entries, ); } /** * Log out of the attached external service. * * @return void */ public function custom_logout() { // Grab plugin settings. $auth_settings = $this->get_plugin_options( SINGLE_ADMIN, 'allow override' ); // Reset option containing old error messages. delete_option( 'auth_settings_advanced_login_error' ); if ( session_id() == '' ) { session_start(); } $current_user_authenticated_by = get_user_meta( get_current_user_id(), 'authenticated_by', true ); // If logged in to CAS, Log out of CAS. if ( $current_user_authenticated_by === 'cas' && $auth_settings['cas'] === '1' ) { if ( ! array_key_exists( 'PHPCAS_CLIENT', $GLOBALS ) || ! array_key_exists( 'phpCAS', $_SESSION ) ) { // Get the CAS server version (default to SAML_VERSION_1_1). // See: https://developer.jasig.org/cas-clients/php/1.3.4/docs/api/group__public.html $cas_version = SAML_VERSION_1_1; if ( $auth_settings['cas_version'] === 'CAS_VERSION_3_0' ) { $cas_version = CAS_VERSION_3_0; } elseif ( $auth_settings['cas_version'] === 'CAS_VERSION_2_0' ) { $cas_version = CAS_VERSION_2_0; } elseif ( $auth_settings['cas_version'] === 'CAS_VERSION_1_0' ) { $cas_version = CAS_VERSION_1_0; } // Set the CAS client configuration if it hasn't been set already. phpCAS::client( $cas_version, $auth_settings['cas_host'], intval( $auth_settings['cas_port'] ), $auth_settings['cas_path'] ); // Restrict logout request origin to the CAS server only (prevent DDOS). phpCAS::handleLogoutRequests( true, array( $auth_settings['cas_host'] ) ); } if ( phpCAS::isAuthenticated() ) { phpCAS::logoutWithRedirectService( get_option( 'siteurl' ) ); } } // If session token set, log out of Google. if ( $current_user_authenticated_by === 'google' || array_key_exists( 'token', $_SESSION ) ) { $token = json_decode( $_SESSION['token'] )->access_token; // Add Google API PHP Client. // @see https://github.com/google/google-api-php-client branch:v1-master require_once dirname( __FILE__ ) . '/vendor/google-api-php-client/src/Google/autoload.php'; // Build the Google Client. $client = new Google_Client(); $client->setApplicationName( 'WordPress' ); $client->setClientId( $auth_settings['google_clientid'] ); $client->setClientSecret( $auth_settings['google_clientsecret'] ); $client->setRedirectUri( 'postmessage' ); // If the hosted domain parameter is set, restrict logins to that domain. if ( array_key_exists( 'google_hosteddomain', $auth_settings ) && strlen( $auth_settings['google_hosteddomain'] ) > 0 ) { $client->setHostedDomain( $auth_settings['google_hosteddomain'] ); } // Revoke the token $client->revokeToken( $token ); // Remove the credentials from the user's session. unset( $_SESSION['token'] ); } } /** * *************************** * Access Restriction * *************************** */ /** * Restrict access to WordPress site based on settings (everyone, logged_in_users). * Hook: parse_request http://codex.wordpress.org/Plugin_API/Action_Reference/parse_request * * @param array $wp WordPress object. * * @return void */ public function restrict_access( $wp ) { // Grab plugin settings. $auth_settings = $this->get_plugin_options( SINGLE_ADMIN, 'allow override' ); // Grab current user. $current_user = wp_get_current_user(); $has_access = ( // Always allow access if WordPress is installing ( defined( 'WP_INSTALLING' ) && isset( $_GET['key'] ) ) || // Always allow access to admins ( current_user_can( 'create_users' ) ) || // Allow access if option is set to 'everyone' ( $auth_settings['access_who_can_view'] == 'everyone' ) || // Allow access to approved external users and logged in users if option is set to 'logged_in_users' ( $auth_settings['access_who_can_view'] == 'logged_in_users' && $this->is_user_logged_in_and_blog_user() && $this->is_email_in_list( $current_user->user_email, 'approved' ) ) || // Allow access for requests to /wp-json/oauth1 so oauth clients can authenticate to use the REST API ( property_exists( $wp, 'matched_query' ) && stripos( $wp->matched_query, "rest_oauth1=" ) === 0 ) || // Allow access for non-GET requests to /wp-json/*, since REST API authentication already covers them ( property_exists( $wp, 'matched_query' ) && stripos( $wp->matched_query, "rest_route=" ) === 0 && $_SERVER['REQUEST_METHOD'] !== 'GET' ) || // Allow access for GET requests to /wp-json/ (root), since REST API discovery calls rely on this ( property_exists( $wp, 'matched_query' ) && $wp->matched_query === 'rest_route=/' ) // Note that GET requests to a rest endpoint will be restricted by authorizer. In that case, error messages will be returned as JSON. ); /** * Developers can use the `authorizer_has_access` filter * to override restricted access on certain pages. Note that the * restriction checks happens before WordPress executes any queries, so * use the global `$wp` variable to investigate what the visitor is * trying to load. * * For example, to unblock an RSS feed, place the following PHP code in * the theme's functions.php file or in a simple plug-in: * * function my_rsa_feed_access_override( $has_access ) { * global $wp; * // check query variables to see if this is the feed * if ( ! empty( $wp->query_vars['feed'] ) ) * $has_access = true; * return $has_access; * } * add_filter( 'authorizer_has_access', 'my_rsa_feed_access_override' ); */ if ( apply_filters( 'authorizer_has_access', $has_access, $wp ) === true ) { // Turn off the public notice about browsing anonymously update_option( 'auth_settings_advanced_public_notice', false ); // We've determined that the current user has access, so simply return to grant access. return $wp; } // Allow HEAD requests to the root (usually discovery from a REST client). if ( $_SERVER['REQUEST_METHOD'] === 'HEAD' && empty( $wp->request ) && empty( $wp->matched_query ) ) { return $wp; } // We've determined that the current user doesn't have access, so we deal with them now. // Fringe case: In a multisite, a user of a different blog can successfully // log in, but they aren't on the 'approved' whitelist for this blog. // If that's the case, add them to the pending list for this blog. if ( is_multisite() && is_user_logged_in() && ! $has_access ) { $current_user = wp_get_current_user(); // Check user access; block if not, add them to pending list if open, let them through otherwise. $result = $this->check_user_access( $current_user, array( $current_user->user_email ) ); } // Check to see if the requested page is public. If so, show it. $current_page_name = property_exists( $wp, 'query_vars' ) && array_key_exists( 'name', $wp->query_vars ) && strlen( $wp->query_vars['name'] ) > 0 ? $wp->query_vars['name'] : ''; if ( ! $current_page_name ) { // Different WordPress versions store the page slug in different places; look for it elsewhere. if ( property_exists( $wp, 'query_vars' ) && array_key_exists( 'pagename', $wp->query_vars ) && strlen( $wp->query_vars['pagename'] ) > 0 ) { $current_page_name = $wp->query_vars['pagename']; } } $current_page_id = empty( $wp->request ) ? 'home' : $this->get_id_from_pagename( $current_page_name ); if ( ! array_key_exists( 'access_public_pages', $auth_settings ) || ! is_array( $auth_settings['access_public_pages'] ) ) { $auth_settings['access_public_pages'] = array(); } if ( in_array( $current_page_id, $auth_settings['access_public_pages'] ) ) { if ( $auth_settings['access_public_warning'] === 'no_warning' ) { update_option( 'auth_settings_advanced_public_notice', false ); } else { update_option( 'auth_settings_advanced_public_notice', true ); } return $wp; } // Check to see if any category assigned to the requested page is public. If so, show it. $current_page_categories = wp_get_post_categories( $current_page_id, array( 'fields' => 'slugs' ) ); foreach( $current_page_categories as $current_page_category ) { if ( in_array( 'cat_' . $current_page_category, $auth_settings['access_public_pages'] ) ) { if ( $auth_settings['access_public_warning'] === 'no_warning' ) { update_option( 'auth_settings_advanced_public_notice', false ); } else { update_option( 'auth_settings_advanced_public_notice', true ); } return $wp; } } // Check to see if this page can't be found. If so, allow showing the 404 page. if ( strlen( $current_page_name ) > 0 && strlen( $current_page_id ) < 1 ) { if ( in_array( 'auth_public_404', $auth_settings['access_public_pages'] ) ) { if ( $auth_settings['access_public_warning'] === 'no_warning' ) { update_option( 'auth_settings_advanced_public_notice', false ); } else { update_option( 'auth_settings_advanced_public_notice', true ); } return $wp; } } // User is denied access, so show them the error message. Render as JSON // if this is a REST API call; otherwise, show the error message via // wp_die() (rendered html), or redirect to the login URL. $current_path = empty( $_SERVER['REQUEST_URI'] ) ? home_url() : $_SERVER['REQUEST_URI']; if ( property_exists( $wp, 'matched_query' ) && stripos( $wp->matched_query, "rest_route=" ) === 0 && $_SERVER['REQUEST_METHOD'] === 'GET' ) { wp_send_json( array( 'code' => 'rest_cannot_view', 'message' => strip_tags( $auth_settings['access_redirect_to_message'] ), 'data' => array( 'status' => 401, ), )); } elseif ( $auth_settings['access_redirect'] === 'message' ) { $page_title = sprintf( /* TRANSLATORS: %s: Name of blog */ __( '%s - Access Restricted', 'authorizer' ), get_bloginfo( 'name' ) ); $error_message = apply_filters( 'the_content', $auth_settings['access_redirect_to_message'] ) . '' . '' . __( 'Log In', 'authorizer' ) . '
'; wp_die( $error_message, $page_title ); } else { // if ( $auth_settings['access_redirect'] === 'login' ) { wp_redirect( wp_login_url( $current_path ), 302 ); exit; } // Sanity check: we should never get here wp_die( 'Access denied.
', 'Site Access Restricted' ); } /** * *************************** * Login page (wp-login.php) * *************************** */ /** * Add custom error message to login screen. * Filter: login_errors */ function show_advanced_login_error( $errors ) { $error = get_option( 'auth_settings_advanced_login_error' ); delete_option( 'auth_settings_advanced_login_error' ); $errors = ' ' . $error . "Can't reach CAS server.
' . __( "You're not currently allowed to log into this site. If you think this is a mistake, please contact your administrator.", 'authorizer' ) . '
'; } if ( ! array_key_exists( 'access_should_email_approved_users', $auth_settings ) ) { $auth_settings['access_should_email_approved_users'] = ''; } if ( ! array_key_exists( 'access_email_approved_users_subject', $auth_settings ) ) { $auth_settings['access_email_approved_users_subject'] = sprintf( /* TRANSLATORS: %s: Shortcode for name of site */ __( 'Welcome to %s!', 'authorizer' ), '[site_name]' ); } if ( ! array_key_exists( 'access_email_approved_users_body', $auth_settings ) ) { $auth_settings['access_email_approved_users_body'] = sprintf( /* TRANSLATORS: 1: Shortcode for user email 2: Shortcode for site name 3: Shortcode for site URL */ __( "Hello %1\$s,\nWelcome to %2\$s! You now have access to all content on the site. Please visit us here:\n%3\$s\n", 'authorizer' ), '[user_email]', '[site_name]', '[site_url]' ); } // Public Access to Private Page Defaults. if ( ! array_key_exists( 'access_who_can_view', $auth_settings ) ) { $auth_settings['access_who_can_view'] = 'everyone'; } if ( ! array_key_exists( 'access_public_pages', $auth_settings ) ) { $auth_settings['access_public_pages'] = array(); } if ( ! array_key_exists( 'access_redirect', $auth_settings ) ) { $auth_settings['access_redirect'] = 'login'; } if ( ! array_key_exists( 'access_public_warning', $auth_settings ) ) { $auth_settings['access_public_warning'] = 'no_warning'; } if ( ! array_key_exists( 'access_redirect_to_message', $auth_settings ) ) { $auth_settings['access_redirect_to_message'] = '' . __( 'Notice: You are browsing this site anonymously, and only have access to a portion of its content.', 'authorizer' ) . '
'; } // External Service Defaults. if ( ! array_key_exists( 'access_default_role', $auth_settings ) ) { // Set default role to 'student' if that role exists, 'subscriber' otherwise. $all_roles = $wp_roles->roles; $editable_roles = apply_filters( 'editable_roles', $all_roles ); if ( array_key_exists( 'student', $editable_roles ) ) { $auth_settings['access_default_role'] = 'student'; } else { $auth_settings['access_default_role'] = 'subscriber'; } } if ( ! array_key_exists( 'google', $auth_settings ) ) { $auth_settings['google'] = ''; } if ( ! array_key_exists( 'cas', $auth_settings ) ) { $auth_settings['cas'] = ''; } if ( ! array_key_exists( 'ldap', $auth_settings ) ) { $auth_settings['ldap'] = ''; } if ( ! array_key_exists( 'google_clientid', $auth_settings ) ) { $auth_settings['google_clientid'] = ''; } if ( ! array_key_exists( 'google_clientsecret', $auth_settings ) ) { $auth_settings['google_clientsecret'] = ''; } if ( ! array_key_exists( 'google_hosteddomain', $auth_settings ) ) { $auth_settings['google_hosteddomain'] = ''; } if ( ! array_key_exists( 'cas_custom_label', $auth_settings ) ) { $auth_settings['cas_custom_label'] = 'CAS'; } if ( ! array_key_exists( 'cas_host', $auth_settings ) ) { $auth_settings['cas_host'] = ''; } if ( ! array_key_exists( 'cas_port', $auth_settings ) ) { $auth_settings['cas_port'] = ''; } if ( ! array_key_exists( 'cas_path', $auth_settings ) ) { $auth_settings['cas_path'] = ''; } if ( ! array_key_exists( 'cas_version', $auth_settings ) ) { $auth_settings['cas_version'] = 'SAML_VERSION_1_1'; } if ( ! array_key_exists( 'cas_attr_email', $auth_settings ) ) { $auth_settings['cas_attr_email'] = ''; } if ( ! array_key_exists( 'cas_attr_first_name', $auth_settings ) ) { $auth_settings['cas_attr_first_name'] = ''; } if ( ! array_key_exists( 'cas_attr_last_name', $auth_settings ) ) { $auth_settings['cas_attr_last_name'] = ''; } if ( ! array_key_exists( 'cas_attr_update_on_login', $auth_settings ) ) { $auth_settings['cas_attr_update_on_login'] = ''; } if ( ! array_key_exists( 'cas_auto_login', $auth_settings ) ) { $auth_settings['cas_auto_login'] = ''; } if ( ! array_key_exists( 'ldap_host', $auth_settings ) ) { $auth_settings['ldap_host'] = ''; } if ( ! array_key_exists( 'ldap_port', $auth_settings ) ) { $auth_settings['ldap_port'] = '389'; } if ( ! array_key_exists( 'ldap_tls', $auth_settings ) ) { $auth_settings['ldap_tls'] = '1'; } if ( ! array_key_exists( 'ldap_search_base', $auth_settings ) ) { $auth_settings['ldap_search_base'] = ''; } if ( ! array_key_exists( 'ldap_uid', $auth_settings ) ) { $auth_settings['ldap_uid'] = 'uid'; } if ( ! array_key_exists( 'ldap_attr_email', $auth_settings ) ) { $auth_settings['ldap_attr_email'] = ''; } if ( ! array_key_exists( 'ldap_user', $auth_settings ) ) { $auth_settings['ldap_user'] = ''; } if ( ! array_key_exists( 'ldap_password', $auth_settings ) ) { $auth_settings['ldap_password'] = ''; } if ( ! array_key_exists( 'ldap_lostpassword_url', $auth_settings ) ) { $auth_settings['ldap_lostpassword_url'] = ''; } if ( ! array_key_exists( 'ldap_attr_first_name', $auth_settings ) ) { $auth_settings['ldap_attr_first_name'] = ''; } if ( ! array_key_exists( 'ldap_attr_last_name', $auth_settings ) ) { $auth_settings['ldap_attr_last_name'] = ''; } if ( ! array_key_exists( 'ldap_attr_update_on_login', $auth_settings ) ) { $auth_settings['ldap_attr_update_on_login'] = ''; } // Advanced defaults. if ( ! array_key_exists( 'advanced_lockouts', $auth_settings ) ) { $auth_settings['advanced_lockouts'] = array( 'attempts_1' => 10, 'duration_1' => 1, 'attempts_2' => 10, 'duration_2' => 10, 'reset_duration' => 120, ); } if ( ! array_key_exists( 'advanced_hide_wp_login', $auth_settings ) ) { $auth_settings['advanced_hide_wp_login'] = ''; } if ( ! array_key_exists( 'advanced_branding', $auth_settings ) ) { $auth_settings['advanced_branding'] = 'default'; } if ( ! array_key_exists( 'advanced_admin_menu', $auth_settings ) ) { $auth_settings['advanced_admin_menu'] = 'top'; } if ( ! array_key_exists( 'advanced_usermeta', $auth_settings ) ) { $auth_settings['advanced_usermeta'] = ''; } if ( ! array_key_exists( 'advanced_override_multisite', $auth_settings ) ) { $auth_settings['advanced_override_multisite'] = ''; } // Save default options to database. update_option( 'auth_settings', $auth_settings ); update_option( 'auth_settings_access_users_pending', $auth_settings_access_users_pending ); update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved ); update_option( 'auth_settings_access_users_blocked', $auth_settings_access_users_blocked ); // Multisite defaults. if ( is_multisite() ) { $auth_multisite_settings = get_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings', array() ); if ( $auth_multisite_settings === FALSE ) { $auth_multisite_settings = array(); } // Global switch for enabling multisite options. if ( ! array_key_exists( 'multisite_override', $auth_multisite_settings ) ) { $auth_multisite_settings['multisite_override'] = ''; } // Access Lists Defaults. $auth_multisite_settings_access_users_approved = get_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings_access_users_approved' ); if ( $auth_multisite_settings_access_users_approved === FALSE ) { $auth_multisite_settings_access_users_approved = array(); } // Login Access Defaults. if ( ! array_key_exists( 'access_who_can_login', $auth_multisite_settings ) ) { $auth_multisite_settings['access_who_can_login'] = 'approved_users'; } // View Access Defaults. if ( ! array_key_exists( 'access_who_can_view', $auth_multisite_settings ) ) { $auth_multisite_settings['access_who_can_view'] = 'everyone'; } // External Service Defaults. if ( ! array_key_exists( 'access_default_role', $auth_multisite_settings ) ) { // Set default role to 'student' if that role exists, 'subscriber' otherwise. $all_roles = $wp_roles->roles; $editable_roles = apply_filters( 'editable_roles', $all_roles ); if ( array_key_exists( 'student', $editable_roles ) ) { $auth_multisite_settings['access_default_role'] = 'student'; } else { $auth_multisite_settings['access_default_role'] = 'subscriber'; } } if ( ! array_key_exists( 'google', $auth_multisite_settings ) ) { $auth_multisite_settings['google'] = ''; } if ( ! array_key_exists( 'cas', $auth_multisite_settings ) ) { $auth_multisite_settings['cas'] = ''; } if ( ! array_key_exists( 'ldap', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap'] = ''; } if ( ! array_key_exists( 'google_clientid', $auth_multisite_settings ) ) { $auth_multisite_settings['google_clientid'] = ''; } if ( ! array_key_exists( 'google_clientsecret', $auth_multisite_settings ) ) { $auth_multisite_settings['google_clientsecret'] = ''; } if ( ! array_key_exists( 'google_hosteddomain', $auth_multisite_settings ) ) { $auth_multisite_settings['google_hosteddomain'] = ''; } if ( ! array_key_exists( 'cas_custom_label', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_custom_label'] = 'CAS'; } if ( ! array_key_exists( 'cas_host', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_host'] = ''; } if ( ! array_key_exists( 'cas_port', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_port'] = ''; } if ( ! array_key_exists( 'cas_path', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_path'] = ''; } if ( ! array_key_exists( 'cas_version', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_version'] = 'SAML_VERSION_1_1'; } if ( ! array_key_exists( 'cas_attr_email', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_attr_email'] = ''; } if ( ! array_key_exists( 'cas_attr_first_name', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_attr_first_name'] = ''; } if ( ! array_key_exists( 'cas_attr_last_name', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_attr_last_name'] = ''; } if ( ! array_key_exists( 'cas_attr_update_on_login', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_attr_update_on_login'] = ''; } if ( ! array_key_exists( 'cas_auto_login', $auth_multisite_settings ) ) { $auth_multisite_settings['cas_auto_login'] = ''; } if ( ! array_key_exists( 'ldap_host', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_host'] = ''; } if ( ! array_key_exists( 'ldap_port', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_port'] = '389'; } if ( ! array_key_exists( 'ldap_tls', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_tls'] = '1'; } if ( ! array_key_exists( 'ldap_search_base', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_search_base'] = ''; } if ( ! array_key_exists( 'ldap_uid', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_uid'] = 'uid'; } if ( ! array_key_exists( 'ldap_attr_email', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_attr_email'] = ''; } if ( ! array_key_exists( 'ldap_user', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_user'] = ''; } if ( ! array_key_exists( 'ldap_password', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_password'] = ''; } if ( ! array_key_exists( 'ldap_lostpassword_url', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_lostpassword_url'] = ''; } if ( ! array_key_exists( 'ldap_attr_first_name', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_attr_first_name'] = ''; } if ( ! array_key_exists( 'ldap_attr_last_name', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_attr_last_name'] = ''; } if ( ! array_key_exists( 'ldap_attr_update_on_login', $auth_multisite_settings ) ) { $auth_multisite_settings['ldap_attr_update_on_login'] = ''; } // Advanced defaults. if ( ! array_key_exists( 'advanced_lockouts', $auth_multisite_settings ) ) { $auth_multisite_settings['advanced_lockouts'] = array( 'attempts_1' => 10, 'duration_1' => 1, 'attempts_2' => 10, 'duration_2' => 10, 'reset_duration' => 120, ); } if ( ! array_key_exists( 'advanced_hide_wp_login', $auth_multisite_settings ) ) { $auth_multisite_settings['advanced_hide_wp_login'] = ''; } // Save default network options to database. update_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings', $auth_multisite_settings ); update_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings_access_users_approved', $auth_multisite_settings_access_users_approved ); } return $auth_settings; } /** * List sanitizer. * $side_effect = 'none' or 'update roles' to make sure WP user roles match * $multisite_mode = 'single' or 'multisite' to indicate which user roles to change (this site or all sites) */ function sanitize_user_list( $list, $side_effect = 'none', $multisite_mode = 'single' ) { // If it's not a list, make it so. if ( ! is_array( $list ) ) { $list = array(); } foreach ( $list as $key => $user_info ) { if ( strlen( $user_info['email'] ) < 1 ) { // Make sure there are no empty entries in the list unset( $list[$key] ); } elseif ( $side_effect === 'update roles' ) { // Make sure the WordPress user accounts have the same role // as that indicated in the list. $wp_user = get_user_by( 'email', $user_info['email'] ); if ( $wp_user ) { if ( is_multisite() && $multisite_mode === 'multisite' ) { foreach ( get_blogs_of_user( $wp_user->ID ) as $blog ) { add_user_to_blog( $blog->userblog_id, $wp_user->ID, $user_info['role'] ); } } else { $wp_user->set_role( $user_info['role'] ); } } } } return $list; } /** * Settings sanitizer callback */ function sanitize_options( $auth_settings ) { // Default to "Approved Users" login access restriction. if ( ! in_array( $auth_settings['access_who_can_login'], array( 'external_users', 'approved_users' ) ) ) { $auth_settings['access_who_can_login'] = 'approved_users'; } // Default to "Everyone" view access restriction. if ( ! in_array( $auth_settings['access_who_can_view'], array( 'everyone', 'logged_in_users' ) ) ) { $auth_settings['access_who_can_view'] = 'everyone'; } // Default to WordPress login access redirect. // Note: this option doesn't exist in multisite options, so we first // check to see if it exists. if ( array_key_exists( 'access_redirect', $auth_settings ) && ! in_array( $auth_settings['access_redirect'], array( 'login', 'page', 'message' ) ) ) { $auth_settings['access_redirect'] = 'login'; } // Default to warning message for anonymous users on public pages. // Note: this option doesn't exist in multisite options, so we first // check to see if it exists. if ( array_key_exists( 'access_public_warning', $auth_settings ) && ! in_array( $auth_settings['access_public_warning'], array( 'no_warning', 'warning' ) ) ) { $auth_settings['access_public_warning'] = 'no_warning'; } // Sanitize Send welcome email (checkbox: value can only be '1' or empty string) $auth_settings['access_should_email_approved_users'] = array_key_exists( 'access_should_email_approved_users', $auth_settings ) && strlen( $auth_settings['access_should_email_approved_users'] ) > 0 ? '1' : ''; // Sanitize Enable Google Logins (checkbox: value can only be '1' or empty string) $auth_settings['google'] = array_key_exists( 'google', $auth_settings ) && strlen( $auth_settings['google'] ) > 0 ? '1' : ''; // Sanitize Enable CAS Logins (checkbox: value can only be '1' or empty string) $auth_settings['cas'] = array_key_exists( 'cas', $auth_settings ) && strlen( $auth_settings['cas'] ) > 0 ? '1' : ''; // Sanitize CAS Host setting $auth_settings['cas_host'] = filter_var( $auth_settings['cas_host'], FILTER_SANITIZE_URL ); // Sanitize CAS Port (int) $auth_settings['cas_port'] = filter_var( $auth_settings['cas_port'], FILTER_SANITIZE_NUMBER_INT ); // Sanitize CAS attribute update (checkbox: value can only be '1' or empty string) $auth_settings['cas_attr_update_on_login'] = array_key_exists( 'cas_attr_update_on_login', $auth_settings ) && strlen( $auth_settings['cas_attr_update_on_login'] ) > 0 ? '1' : ''; // Sanitize CAS auto-login (checkbox: value can only be '1' or empty string) $auth_settings['cas_auto_login'] = array_key_exists( 'cas_auto_login', $auth_settings ) && strlen( $auth_settings['cas_auto_login'] ) > 0 ? '1' : ''; // Sanitize Enable LDAP Logins (checkbox: value can only be '1' or empty string) $auth_settings['ldap'] = array_key_exists( 'ldap', $auth_settings ) && strlen( $auth_settings['ldap'] ) > 0 ? '1' : ''; // Sanitize LDAP Host setting $auth_settings['ldap_host'] = filter_var( $auth_settings['ldap_host'], FILTER_SANITIZE_URL ); // Sanitize LDAP Port (int) $auth_settings['ldap_port'] = filter_var( $auth_settings['ldap_port'], FILTER_SANITIZE_NUMBER_INT ); // Sanitize LDAP TLS (checkbox: value can only be '1' or empty string) $auth_settings['ldap_tls'] = array_key_exists( 'ldap_tls', $auth_settings ) && strlen( $auth_settings['ldap_tls'] ) > 0 ? '1' : ''; // Sanitize LDAP attributes (basically make sure they don't have any parentheses) $auth_settings['ldap_uid'] = filter_var( $auth_settings['ldap_uid'], FILTER_SANITIZE_EMAIL ); // Sanitize LDAP Lost Password URL $auth_settings['ldap_lostpassword_url'] = filter_var( $auth_settings['ldap_lostpassword_url'], FILTER_SANITIZE_URL ); // Obfuscate LDAP directory user password if ( strlen( $auth_settings['ldap_password'] ) > 0 ) { // encrypt the directory user password for some minor obfuscation in the database. $auth_settings['ldap_password'] = base64_encode( $this->encrypt( $auth_settings['ldap_password'] ) ); } // Sanitize LDAP attribute update (checkbox: value can only be '1' or empty string) $auth_settings['ldap_attr_update_on_login'] = array_key_exists( 'ldap_attr_update_on_login', $auth_settings ) && strlen( $auth_settings['ldap_attr_update_on_login'] ) > 0 ? '1' : ''; // Make sure public pages is an empty array if it's empty // Note: this option doesn't exist in multisite options, so we first // check to see if it exists. if ( array_key_exists( 'access_public_pages', $auth_settings ) && ! is_array( $auth_settings['access_public_pages'] ) ) { $auth_settings['access_public_pages'] = array(); } // Make sure all lockout options are integers (attempts_1, // duration_1, attempts_2, duration_2, reset_duration). foreach ( $auth_settings['advanced_lockouts'] as $key => $value ) { $auth_settings['advanced_lockouts'][$key] = filter_var( $value, FILTER_SANITIZE_NUMBER_INT ); } // Sanitize Hide WordPress logins (checkbox: value can only be '1' or empty string) $auth_settings['advanced_hide_wp_login'] = array_key_exists( 'advanced_hide_wp_login', $auth_settings ) && strlen( $auth_settings['advanced_hide_wp_login'] ) > 0 ? '1' : ''; // Sanitize Override multisite options (checkbox: value can only be '1' or empty string) $auth_settings['advanced_override_multisite'] = array_key_exists( 'advanced_override_multisite', $auth_settings ) && strlen( $auth_settings['advanced_override_multisite'] ) > 0 ? '1' : ''; return $auth_settings; } /** * Keep authorizer approved users' roles in sync with WordPress roles * if someone changes the role via the WordPress Edit User options page. * * @action edit_user_profile_update * @ref https://codex.wordpress.org/Plugin_API/Action_Reference/edit_user_profile_update * @param int $user_id The user ID of the user being edited * @action personal_options_update * @ref https://codex.wordpress.org/Plugin_API/Action_Reference/personal_options_update * @param int $user_id The user ID of the user being edited */ function edit_user_profile_update_role( $user_id ) { if ( ! current_user_can( 'edit_user', $user_id ) ) { return; } // If user is in approved list, update his/her associated role. $wp_user = get_user_by( 'id', $user_id ); if ( $this->is_email_in_list( $wp_user->get( 'user_email' ), 'approved' ) ) { $auth_settings_access_users_approved = $this->sanitize_user_list( $this->get_plugin_option( 'access_users_approved', SINGLE_ADMIN ) ); // Find approved user and sync with the corresponding WP_User. foreach ( $auth_settings_access_users_approved as $key => $user ) { if ( $user['email'] === $wp_user->user_email ) { // Sync user role. if ( array_key_exists( 'role', $_REQUEST ) ) { $auth_settings_access_users_approved[$key]['role'] = $_REQUEST['role']; } // Sync email address. if ( array_key_exists( 'email', $_REQUEST ) ) { $auth_settings_access_users_approved[$key]['email'] = $_REQUEST['email']; } } } update_option( 'auth_settings_access_users_approved', $auth_settings_access_users_approved ); } } /** * Settings print callbacks */ function print_section_info_tabs( $args = '' ) { if ( MULTISITE_ADMIN === $this->get_admin_mode( $args )): ?>| (get_user_count_from_list( 'pending', $admin_mode ); ?>) | print_combo_auth_access_users_pending(); ?> |
|---|---|
| (get_user_count_from_list( 'approved', $admin_mode ); ?>) | print_combo_auth_access_users_approved(); ?> |
| (get_user_count_from_list( 'blocked', $admin_mode ); ?>) | print_combo_auth_access_users_blocked(); ?> |
Note for theme developers: Add more options here by using the `authorizer_add_branding_option` filter in your theme. You can see an example theme that implements this filter in the plugin directory under sample-theme-add-branding.', 'authorizer' ); ?>
get_plugin_option( $option ); // Print option elements. ?> />' . __( "Pending Users: Pending users are users who have successfully logged in to the site, but who haven't yet been approved (or blocked) by you.", 'authorizer' ) .'
' . __( "Approved Users: Approved users have access to the site once they successfully log in.", 'authorizer' ) . '
' . __( "Blocked Users: Blocked users will receive an error message when they try to visit the site after authenticating.", 'authorizer' ) . '
' . __( "Users in the Pending list appear automatically after a new user tries to log in from the configured external authentication service. You can add users to the Approved or Blocked lists by typing them in manually, or by clicking the Approve or Block buttons next to a user in the Pending list.", 'authorizer' ) . '
'; $screen->add_help_tab( array( 'id' => 'help_auth_settings_access_lists_content', 'title' => __( 'Access Lists', 'authorizer' ), 'content' => $help_auth_settings_access_lists_content, ) ); // Add help tab for Login Access Settings $help_auth_settings_access_login_content = '' . __( "Who can log in to the site?: Choose the level of access restriction you'd like to use on your site here. You can leave the site open to anyone with a WordPress account or an account on an external service like Google, CAS, or LDAP, or restrict it to WordPress users and only the external users that you specify via the Access Lists.", 'authorizer' ) . '
' . __( "Which role should receive email notifications about pending users?: If you've restricted access to approved users, you can determine which WordPress users will receive a notification email everytime a new external user successfully logs in and is added to the pending list. All users of the specified role will receive an email, and the external user will get a message (specified below) telling them their access is pending approval.", 'authorizer' ) . '
' . __( 'What message should pending users see after attempting to log in?: Here you can specify the exact message a new external user will see once they try to log in to the site for the first time.', 'authorizer' ) . '
'; $screen->add_help_tab( array( 'id' => 'help_auth_settings_access_login_content', 'title' => __( 'Login Access', 'authorizer' ), 'content' => $help_auth_settings_access_login_content, ) ); // Add help tab for Public Access Settings $help_auth_settings_access_public_content = '' . __( "Who can view the site?: You can restrict the site's visibility by only allowing logged in users to see pages. If you do so, you can customize the specifics about the site's privacy using the settings below.", 'authorizer' ) . '
' . __( "What pages (if any) should be available to everyone?: If you'd like to declare certain pages on your site as always public (such as the course syllabus, introduction, or calendar), specify those pages here. These pages will always be available no matter what access restrictions exist.", 'authorizer' ) . '
' . __( "What happens to people without access when they visit a private page?: Choose the response anonymous users receive when visiting the site. You can choose between immediately taking them to the login screen, or simply showing them a message.", 'authorizer' ) . '
' . __( "What happens to people without access when they visit a public page?: Choose the response anonymous users receive when visiting a page on the site marked as public. You can choose between showing them the page without any message, or showing them a the page with a message above the content.", 'authorizer' ) . '
' . __( "What message should people without access see?: If you chose to show new users a message above, type that message here.", 'authorizer' ) . '
'; $screen->add_help_tab( array( 'id' => 'help_auth_settings_access_public_content', 'title' => __( 'Public Access', 'authorizer' ), 'content' => $help_auth_settings_access_public_content, ) ); // Add help tab for External Service (CAS, LDAP) Settings $help_auth_settings_external_content = '' . __( "Type of external service to authenticate against: Choose which authentication service type you will be using. You'll have to fill out different fields below depending on which service you choose.", 'authorizer' ) . '
' . __( "Enable Google Logins: Choose if you want to allow users to log in with their Google Account credentials. You will need to enter your API Client ID and Secret to enable Google Logins.", 'authorizer' ) . '
' . __( "Enable CAS Logins: Choose if you want to allow users to log in with via CAS (Central Authentication Service). You will need to enter details about your CAS server (host, port, and path) to enable CAS Logins.", 'authorizer' ) . '
' . __( "Enable LDAP Logins: Choose if you want to allow users to log in with their LDAP (Lightweight Directory Access Protocol) credentials. You will need to enter details about your LDAP server (host, port, search base, uid attribute, directory user, directory user password, and whether to use TLS) to enable Google Logins.", 'authorizer' ) . '
' . __( "Default role for new CAS users: Specify which role new external users will get by default. Be sure to choose a role with limited permissions!", 'authorizer' ) . '
' . __( "If you enable Google logins:", 'authorizer' ) . '
' . __( "If you enable CAS logins:", 'authorizer' ) . '
' . __( "If you enable LDAP logins:", 'authorizer' ) . '
' . __( "Limit invalid login attempts: Choose how soon (and for how long) to restrict access to individuals (or bots) making repeated invalid login attempts. You may set a shorter delay first, and then a longer delay after repeated invalid attempts; you may also set how much time must pass before the delays will be reset to normal.", 'authorizer' ) . '
' . __( "Hide WordPress Logins: If you want to hide the WordPress username and password fields and the Log In button on the wp-login screen, enable this option. Note: You can always access the WordPress logins by adding external=wordpress to the wp-login URL, like so:", 'authorizer' ) . ' ' . wp_login_url() . '?external=wordpress.
' . __( "Custom WordPress login branding: If you'd like to use custom branding on the WordPress login page, select that here. You will need to use the `authorizer_add_branding_option` filter in your theme to add it. You can see an example theme that implements this filter in the plugin directory under sample-theme-add-branding.", 'authorizer' ) . '
'; $screen->add_help_tab( array( 'id' => 'help_auth_settings_advanced_content', 'title' => __( 'Advanced', 'authorizer' ), 'content' => $help_auth_settings_advanced_content, ) ); } /** * *************************** * Multisite: Network Admin Options page * *************************** */ /** * Network Admin menu item * Hook: network_admin_menu * * @param none * @return void */ public function network_admin_menu() { // @see http://codex.wordpress.org/Function_Reference/add_menu_page add_menu_page( 'Authorizer', // Page title 'Authorizer', // Menu title 'manage_network_options', // Capability 'authorizer', // Menu slug array( $this, 'create_network_admin_page' ), 'dashicons-groups', // Icon URL 89 // Position ); } /** * Output the HTML for the options page */ public function create_network_admin_page() { if ( ! current_user_can( 'manage_network_options' ) ) { wp_die( __( 'You do not have sufficient permissions to access this page.', 'authorizer' ) ); } $auth_settings = get_blog_option( BLOG_ID_CURRENT_SITE, 'auth_multisite_settings', array() ); ?>