diff --git a/README.md b/README.md index afb4fbb..bb1f844 100644 --- a/README.md +++ b/README.md @@ -138,72 +138,72 @@ The different fields that can be defined in the settings JSON in **Settings** > Users are matched by their email address in WordPress, and whichever role they have in WordPress is maintained. -| Setting | Example value -| --- | --- -| Display name | Contoso -| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 -| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= -| Reply URL | https://www.example.com/blog/wp-login.php -| Field to match to UPN | Email Address +| Setting | Example value | +| --------------------- | -------------------------------------------- | +| Display name | Contoso | +| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 | +| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= | +| Redirect URL | https://www.example.com/blog/wp-login.php | +| Field to match to UPN | Email Address | ### Match on username alias Users are matched by their login names in WordPress and the alias portion of their Azure AD UserPrincipalName. Whichever role they have in WordPress is maintained. -| Setting | Example value -| --- | --- -| Display name | Contoso -| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 -| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= -| Reply URL | https://www.example.com/blog/wp-login.php -| Field to match to UPN | Login Name -| Match on alias of the UPN | Yes +| Setting | Example value | +| ------------------------- | -------------------------------------------- | +| Display name | Contoso | +| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 | +| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= | +| Redirect URL | https://www.example.com/blog/wp-login.php | +| Field to match to UPN | Login Name | +| Match on alias of the UPN | Yes | ### Group membership-based roles, no default role Users are matched by their login names in WordPress, and WordPress roles are dictated by membership to a given Azure AD group. Access is denied if they are not members of any of these groups. -| Setting | Example value -| --- | --- -| Display name | Contoso -| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 -| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= -| Reply URL | https://www.example.com/blog/wp-login.php -| Field to match to UPN | Login Name -| Enable Azure AD group to WP role association | Yes -| Default WordPress role if not in Azure AD group | (None, deny access) -| WordPress role to Azure AD group map |
Administrator5d1915c4-2373-42ba-9796-7c092fa1dfc6
Editor21c0f87b-4b65-48c1-9231-2f9295ef601c
Authorf5784693-11e5-4812-87db-8c6e51a18ffd
Contributor780e055f-7e64-4e34-9ff3-012910b7e5ad
Subscriberf1be9515-0aeb-458a-8c0a-30a03c1afb67
+| Setting | Example value | +| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Display name | Contoso | +| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 | +| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= | +| Redirect URL | https://www.example.com/blog/wp-login.php | +| Field to match to UPN | Login Name | +| Enable Azure AD group to WP role association | Yes | +| Default WordPress role if not in Azure AD group | (None, deny access) | +| WordPress role to Azure AD group map |
Administrator5d1915c4-2373-42ba-9796-7c092fa1dfc6
Editor21c0f87b-4b65-48c1-9231-2f9295ef601c
Authorf5784693-11e5-4812-87db-8c6e51a18ffd
Contributor780e055f-7e64-4e34-9ff3-012910b7e5ad
Subscriberf1be9515-0aeb-458a-8c0a-30a03c1afb67
| ### Group membership-based roles with default role Users are matched by their login names in WordPress, and WordPress roles are dictated by membership to a given Azure AD group. If the user is not a part of any of these groups, they are assigned the *Author* role. -| Setting | Example value -| --- | --- -| Display name | Contoso -| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 -| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= -| Reply URL | https://www.example.com/blog/wp-login.php -| Field to match to UPN | Login Name -| Enable Azure AD group to WordPress role association | Yes -| Default WordPress role if not in Azure AD group | Author -| WordPress role to Azure AD group map |
Administrator5d1915c4-2373-42ba-9796-7c092fa1dfc6
Editor21c0f87b-4b65-48c1-9231-2f9295ef601c
Authorf5784693-11e5-4812-87db-8c6e51a18ffd
Contributor780e055f-7e64-4e34-9ff3-012910b7e5ad
Subscriberf1be9515-0aeb-458a-8c0a-30a03c1afb67
+| Setting | Example value | +| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Display name | Contoso | +| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 | +| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= | +| Reply URL | https://www.example.com/blog/wp-login.php | +| Field to match to UPN | Login Name | +| Enable Azure AD group to WordPress role association | Yes | +| Default WordPress role if not in Azure AD group | Author | +| WordPress role to Azure AD group map |
Administrator5d1915c4-2373-42ba-9796-7c092fa1dfc6
Editor21c0f87b-4b65-48c1-9231-2f9295ef601c
Authorf5784693-11e5-4812-87db-8c6e51a18ffd
Contributor780e055f-7e64-4e34-9ff3-012910b7e5ad
Subscriberf1be9515-0aeb-458a-8c0a-30a03c1afb67
| ### Group membership-based roles, default role, auto-provision Users are matched by their email in WordPress, and WordPress roles are dictated by membership to a given Azure AD group. If the user doesn't exist in WordPress yet, they will be auto-provisioned. If the user is not a part of any of these groups, they are assigned the *Subscriber* role. -| Setting | Example value -| --- | --- -| Display name | Contoso -| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 -| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= -| Reply URL | https://www.example.com/blog/wp-login.php -| Field to match to UPN | Email Address -| Enable auto-provisioning | Yes -| Enable Azure AD group to WP role association | Yes -| Default WordPress role if not in Azure AD group | Subscriber -| WordPress role to Azure AD group map |
Administrator5d1915c4-2373-42ba-9796-7c092fa1dfc6
Editor21c0f87b-4b65-48c1-9231-2f9295ef601c
Authorf5784693-11e5-4812-87db-8c6e51a18ffd
Contributor780e055f-7e64-4e34-9ff3-012910b7e5ad
Subscriberf1be9515-0aeb-458a-8c0a-30a03c1afb67
+| Setting | Example value | +| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Display name | Contoso | +| Client ID | 9054eff5-bfef-4cc5-82fd-8c35534e48f9 | +| Client Secret | NTY5MmE5YjMwMGY2MWQ0NjU5MzYxNjdjNzE1OGNiZmY= | +| Reply URL | https://www.example.com/blog/wp-login.php | +| Field to match to UPN | Email Address | +| Enable auto-provisioning | Yes | +| Enable Azure AD group to WP role association | Yes | +| Default WordPress role if not in Azure AD group | Subscriber | +| WordPress role to Azure AD group map |
Administrator5d1915c4-2373-42ba-9796-7c092fa1dfc6
Editor21c0f87b-4b65-48c1-9231-2f9295ef601c
Authorf5784693-11e5-4812-87db-8c6e51a18ffd
Contributor780e055f-7e64-4e34-9ff3-012910b7e5ad
Subscriberf1be9515-0aeb-458a-8c0a-30a03c1afb67
| ## Groups @@ -228,4 +228,77 @@ Most of the OpenID Connect endpoints and configuration (e.g. signing keys, etc.) If you've configured this plugin to automatically redirect to Azure AD for sign-in, but something is misconfigured, you may find yourself locked out of your site's admin dashboard. -To log in to your site *without* automatically redirecting to Azure AD (thus giving you an opportunity to enter a regular username and password), you can append `?aadsso_no_redirect=please` to the login URL. For example, if your login URL is `https://example.com/wp-login.php`, navigating to `https://example.com/wp-login.php?aadsso_no_redirect=please` will prevent any automatic redirects. \ No newline at end of file +To log in to your site *without* automatically redirecting to Azure AD (thus giving you an opportunity to enter a regular username and password), you can append `?aadsso_no_redirect=please` to the login URL. For example, if your login URL is `https://example.com/wp-login.php`, navigating to `https://example.com/wp-login.php?aadsso_no_redirect=please` will prevent any automatic redirects. + +### Constants Support + +*since 0.8.0* + +The plugin can be configured with defined constants. This allows for zero-database configuration, suitable for automated deployments, or deployments using environment variables. + +Constants must be primitive/scalar values (string, number, bool, int), so if you use `AADSSO_ENABLE_AAD_GROUP_TO_WP_ROLE` and `AADSSO_AAD_GROUP_TO_WP_ROLE_MAP`, the value of `AADSSO_AAD_GROUP_TO_WP_ROLE_MAP` must be a JSON-encoded string. + +| Constant Name | Type | Description | Default | +| -------------------------------------- | ------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | +| `AADSSO_ORG_DISPLAY_NAME` | string | Human-friendly name to describe your organization. | `get_bloginfo('name')` | +| `AADSSO_ORG_DOMAIN_HINT` | string | tenant or Azure AD directory id. | +| `AADSSO_CLIENT_ID` | string | Azure AD-provided GUID identifying your WordPress application. | +| `AADSSO_CLIENT_SECRET` | string | Azure AD-provided secret. | +| `AADSSO_REDIRECT_URI` | string | URL to redirect to when a user authenticates successfully. | `wp_login_url()` | +| `AADSSO_LOGOUT_REDIRECT_URI` | string | URL to redirect to when a user logs out. | `wp_login_url()` | +| `AADSSO_ENABLE_FULL_LOGOUT` | bool | When enabled, a WordPress logout will also trigger an Azure AD logout. | `false` | +| `AADSSO_FIELD_TO_MATCH_TO_UPN` | string | WordPress field to map to Azure AD UPN | `'email'` | +| `AADSSO_MATCH_ON_UPN_ALIAS` | type | Allow `user_login` to be matched on on the user portion of an email address. | `false` | +| `AADSSO_ENABLE_AUTO_PROVISIONING` | type | `true` to allow WordPress to create new users for Azure AD users automatically. | `false` | +| `AADSSO_ENABLE_AUTO_FORWARD_TO_AAD` | type | `true` to skip the WordPress `/wp-login.php` screen entirely. | `false` | +| `AADSSO_ENABLE_AAD_GROUP_TO_WP_ROLE` | type | `true` to map Azure AD groups to WordPress roles automatically. | `false` | +| `AADSSO_DEFAULT_WP_ROLE` | type | WordPress role slug for auto-provisioned users. When `null`, access is denied. | `null` | +| `AADSSO_AAD_GROUP_TO_WP_ROLE_MAP` | type | Map of Azure AD Group ID to WordPress role slugs. **Constant value be JSON encoded** | `{}` | +| `AADSSO_GRAPH_ENDPOINT` | type | Endpoint for fetching information about users and groups. | `https://graph.microsoft.com` | +| `AADSSO_GRAPH_VERSION` | type | Version of Microsoft Graph to use. | `v1.0` | +| `AADSSO_OPENID_CONFIGURATION_ENDPOINT` | type | `.well-known` endpoint to retrieve automatic OpenID configuration from. | `https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration` | +| `AADSSO_AUTHORIZATION_ENDPOINT` | type | By default, this is detected automatically. | `null` | +| `AADSSO_END_SESSION_ENDPOINT` | type | By default, this is detected automatically. | `null` | +| `AADSSO_JWKS_URI` | type | By default, this is detected automatically. | `null` | +| `AADSSO_TOKEN_ENDPOINT` | type | By default, this is detected automatically. | `null` | + +#### Example `wp-config.php` with Azure AD Group-to-Role Mapping + +```php + 'administrator', + 'a0b1c2d3-4e5f-4a7b-ac9d-0e1f2a3b4c5d' => 'editor', + ) + ) +); +``` + +#### Reset Constant + +The plugin's settings can be reset with the `AADSSO_RESET_SETTINGS` constant. When `true`, the plugin will delete all of its options from the database. This has no effect on settings defined with constants. + +```php + +### Must-Use Plugin Support + +*since 0.8.0* + +The plugin now supports deployment as a must-use plugin. Coupled with constants for configuration, you can rapidly provision unattended Azure AD SSO WordPress installations. To be activated, the plugin should be placed in `wp-content/mu-plugins` and a `wp-content/mu-plugins/load-aad-sso-wordpress.php` file should be created. + +```php + get_bloginfo( 'name' ), - 'field_to_match_to_upn' => 'email', - 'default_wp_role' => null, - 'enable_auto_provisioning' => false, - 'match_on_upn_alias' => false, - 'enable_auto_forward_to_aad' => false, - 'enable_aad_group_to_wp_role' => false, - 'redirect_uri' => wp_login_url(), - 'logout_redirect_uri' => wp_login_url(), - 'openid_configuration_endpoint' => 'https://login.microsoftonline.com/common/.well-known/openid-configuration', - ); - - if ( null === $key ) { - return $defaults; - } else { - if ( isset( $defaults[ $key ] ) ) { - return $defaults[ $key ]; - } else { - return null; - } - } - } - - /** - * Gets the (only) instance of the plugin. - * - * @return self The (only) instance of the class. - */ - public static function get_instance() { - if ( ! self::$instance ) { - self::$instance = new self; - } - return self::$instance; - } - - /** - * Initializes values for using stored settings and cached Azure AD configuration. - * - * @return \AADSSO_Settings The (only) configured instance of this class. - */ - public static function init() { - - $instance = self::get_instance(); - - // First, retrieve the settings stored in the WordPress database. - $instance->load_settings( get_option( 'aadsso_settings' ) ); - - /* - * Then, add the settings stored in the OpenID Connect configuration endpoint. - * We're using transient as a cache, to prevent from making a request on every WP page load. - * Default transient expiration is one hour (3600 seconds), but in case a forced load is - * required, adding aadsso_reload_openid_config=1 in the URL will do the trick. - */ - $openid_configuration = get_transient( 'aadsso_openid_configuration' ); - if( false === $openid_configuration || isset( $_GET['aadsso_reload_openid_config'] ) ) { - $openid_configuration = json_decode( - self::get_remote_contents( $instance->openid_configuration_endpoint ), - true // Return associative array - ); - set_transient( 'aadsso_openid_configuration', $openid_configuration, 3600 ); - } - $instance->load_settings( $openid_configuration ); - - return $instance; - } - - /** - * Loads contents of a text file (local or remote). - * - * @param string $file_path The path to the file. May be local or remote. - * - * @return string The contents of the file. - */ - public static function get_remote_contents( $file_path ) { - - $response = wp_remote_get( $file_path ); - $file_contents = wp_remote_retrieve_body( $response ); - - return $file_contents; - } - - /** - * Sets provided settings inside the current instance. - * - * @param array $settings An associative array of settings to be added to current configuration. - * - * @return \AADSSO_Settings The current (only) instance with new configuration. - */ - function load_settings( $settings ) { - - // Expecting $settings to be an associative array. Do nothing if it isn't. - if ( ! is_array( $settings ) || empty( $settings ) ) { - return $this; - } - - /* - * Invert the => map (which is what is stored in the database) to a flat - * => map is used during the authorization check. If a group appears twice, the first - * occurence (the first role) will take precedence. - */ - if( ! empty( $settings['role_map'] ) ) { - $settings['aad_group_to_wp_role_map'] = array(); - foreach ( $settings['role_map'] as $role_slug => $group_ids_list ) { - $group_ids = explode( ',', $group_ids_list ); - if ( ! empty( $group_ids ) ) { - foreach ( $group_ids as $group_id ) { - if ( ! isset( $settings['aad_group_to_wp_role_map'][ $group_id ] ) ) { - $settings['aad_group_to_wp_role_map'][ $group_id ] = $role_slug; - } - } - } - } - } - - // Overwrite any provided setting values. - foreach ( $settings as $key => $value ) { - if ( property_exists( $this, $key ) ) { - $this->{$key} = $value; - } - } - return $this; - } -} diff --git a/SettingsPage.php b/SettingsPage.php deleted file mode 100644 index 802e515..0000000 --- a/SettingsPage.php +++ /dev/null @@ -1,715 +0,0 @@ -settings = get_option( 'aadsso_settings', $default_settings ); - foreach ( $default_settings as $key => $default_value ) { - if ( ! isset( $this->settings[ $key ] ) ) { - $this->settings[ $key ] = $default_value; - } - } - } - - /** - * Clears settings if $_GET['aadsso_nonce'] is set and if the nonce is valid. - */ - public function maybe_reset_settings() - { - $should_reset_settings = isset( $_GET['aadsso_nonce'] ) - && wp_verify_nonce( $_GET['aadsso_nonce'], 'aadsso_reset_settings' ); - if ( $should_reset_settings ) { - delete_option( 'aadsso_settings' ); - wp_redirect( admin_url( 'options-general.php?page=aadsso_settings&aadsso_reset=success' ) ); - } - } - - /** - * Migrates old settings (Settings.json) to the database and attempts to delete the old settings file. - */ - public function maybe_migrate_settings() { - /** - * Settings should only be migrated if - * - The request came from a nonced link - * - The nonce action is 'aadsso_migrate_settings' - * - There is a legacy settings path defined - * - There is a file at that legacy settings path - */ - $should_migrate_settings = isset( $_GET['aadsso_nonce'] ) - && wp_verify_nonce( $_GET['aadsso_nonce'], 'aadsso_migrate_from_json' ) - && defined( 'AADSSO_SETTINGS_PATH' ) - && file_exists( AADSSO_SETTINGS_PATH ); - - if ( $should_migrate_settings ) { - - $legacy_settings = json_decode( file_get_contents( AADSSO_SETTINGS_PATH ), true ); - - if ( null === $legacy_settings ) { - wp_redirect( admin_url( 'options-general.php?page=aadsso_settings&aadsso_migrate_from_json_status=invalid_json') ); - } - - // If aad_group_to_wp_role_map is set in the legacy settings, build the inverted role_map array, - // which is what is ultimately saved in the database. - if ( isset( $legacy_settings['aad_group_to_wp_role_map'] ) ) { - $legacy_settings['role_map'] = array(); - foreach ($aad_group_to_wp_role_map as $group_id => $role_slug ) { - if ( ! isset( $legacy_settings['role_map'][$role_slug] ) ) { - $legacy_settings['role_map'][$role_slug] = $group_id; - } else { - $legacy_settings['role_map'][$role_slug] .= ',' . $group_id; - } - } - } - - $sanitized_settings = $this->sanitize_settings( $legacy_settings ); - - update_option( 'aadsso_settings', $sanitized_settings ); - - if ( is_writable( AADSSO_SETTINGS_PATH ) && is_writable( dirname( AADSSO_SETTINGS_PATH ) ) && unlink( AADSSO_SETTINGS_PATH ) ) { - wp_redirect( admin_url( 'options-general.php?page=aadsso_settings&aadsso_migrate_from_json_status=success' ) ); - } else { - wp_redirect( admin_url( 'options-general.php?page=aadsso_settings&aadsso_migrate_from_json_status=manual' ) ); - } - } - } - - /** - * Shows messages about the state of the migration operation - */ - public function notify_json_migrate_status() { - if( isset( $_GET['aadsso_migrate_from_json_status'] ) ) { - if( 'success' === $_GET['aadsso_migrate_from_json_status'] ) { - echo '

' - . __( 'Legacy settings have been migrated and the old configuration file has been deleted.', 'aad-sso-wordpress' ) - . __('To finish migration, unset AADSSO_SETTINGS_PATH from wp-config.php. ', 'aad-sso-wordpress') - .'

'; - } elseif ( 'manual' === $_GET['aadsso_migrate_from_json_status'] ) { - echo '

' - . esc_html__( 'Legacy settings have been migrated successfully. ', 'aad-sso-wordpress' ) - . sprintf( __('To finish migration, delete the file at the path %s. ', 'aad-sso-wordpress'), AADSSO_SETTINGS_PATH ) - . sprintf( __('Then, unset AADSSO_SETTINGS_PATH from wp-config.php. ', 'aad-sso-wordpress') ) - .'

'; - } elseif( 'invalid_json' === $_GET['aadsso_migrate_from_json_status'] ) { - echo '

' - . sprintf( __('Legacy settings could not be migrated from %s. ', 'aad-sso-wordpress'), AADSSO_SETTINGS_PATH ) - . esc_html( 'File could not be parsed as JSON. ', 'aad-sso-wordpress' ) - . esc_html( 'Delete the file, or check its syntax.', 'aad-sso-wordpress' ) - .'

'; - } - } - } - - /** - * Notifies user if settings reset was successful. - */ - public function notify_if_reset_successful() - { - if ( isset( $_GET['aadsso_reset'] ) && 'success' === $_GET['aadsso_reset'] ) { - echo '

' - . __( 'Single Sign-on with Azure Active Directory settings have been reset to default.', - 'aad-sso-wordpress' ) - .'

'; - } - } - - /** - * Adds the 'Azure AD' options page. - */ - public function add_options_page() { - $this->options_page_id = add_options_page( - __( 'Azure Active Directory Settings', 'aad-sso-wordpress' ), // page_title - 'Azure AD', // menu_title - 'manage_options', // capability - 'aadsso_settings', // menu_slug - array( $this, 'render_admin_page' ) // function - ); - } - - /** - * Renders the 'Azure AD' settings page. - */ - public function render_admin_page() { - require_once( 'view/settings.php' ); - } - - /** - * Registers settings, sections and fields. - */ - public function register_settings() { - - register_setting( - 'aadsso_settings', // option_group - 'aadsso_settings', // option_name - array( $this, 'sanitize_settings' ) // sanitize_callback - ); - - add_settings_section( - 'aadsso_settings_general', // id - __( 'General', 'aad-sso-wordpress' ), // title - array( $this, 'settings_general_info' ), // callback - 'aadsso_settings_page' // page - ); - - add_settings_section( - 'aadsso_settings_advanced', // id - __( 'Advanced', 'aad-sso-wordpress' ), // title - array( $this, 'settings_advanced_info' ), // callback - 'aadsso_settings_page' // page - ); - - add_settings_field( - 'org_display_name', // id - __( 'Display name', 'aad-sso-wordpress' ), // title - array( $this, 'org_display_name_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'org_domain_hint', // id - __( 'Domain hint', 'aad-sso-wordpress' ), // title - array( $this, 'org_domain_hint_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'client_id', // id - __( 'Client ID', 'aad-sso-wordpress' ), // title - array( $this, 'client_id_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'client_secret', // id - __( 'Client secret', 'aad-sso-wordpress' ), // title - array( $this, 'client_secret_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'redirect_uri', // id - __( 'Redirect URL', 'aad-sso-wordpress' ), // title - array( $this, 'redirect_uri_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'logout_redirect_uri', // id - __( 'Logout redirect URL', 'aad-sso-wordpress' ), // title - array( $this, 'logout_redirect_uri_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'enable_full_logout', // id - __( 'Enable full logout', 'aad-sso-wordpress' ), // title - array( $this, 'enable_full_logout_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'field_to_match_to_upn', // id - __( 'Field to match to UPN', 'aad-sso-wordpress' ), // title - array( $this, 'field_to_match_to_upn_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'match_on_upn_alias', // id - __( 'Match on alias of the UPN', 'aad-sso-wordpress' ), // title - array( $this, 'match_on_upn_alias_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'enable_auto_provisioning', // id - __( 'Enable auto-provisioning', 'aad-sso-wordpress' ), // title - array( $this, 'enable_auto_provisioning_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'enable_auto_forward_to_aad', // id - __( 'Enable auto-forward to Azure AD', 'aad-sso-wordpress' ), // title - array( $this, 'enable_auto_forward_to_aad_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'enable_aad_group_to_wp_role', // id - __( 'Enable Azure AD group to WP role association', 'aad-sso-wordpress' ), // title - array( $this, 'enable_aad_group_to_wp_role_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'default_wp_role', // id - __( 'Default WordPress role if not in Azure AD group', 'aad-sso-wordpress' ), // title - array( $this, 'default_wp_role_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'role_map', // id - __( 'WordPress role to Azure AD group map', 'aad-sso-wordpress' ), // title - array( $this, 'role_map_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_general' // section - ); - - add_settings_field( - 'openid_configuration_endpoint', // id - __( 'OpenID Connect configuration endpoint', 'aad-sso-wordpress' ), // title - array( $this, 'openid_configuration_endpoint_callback' ), // callback - 'aadsso_settings_page', // page - 'aadsso_settings_advanced' // section - ); - } - - /** - * Gets the array of roles determined by other plugins to be "editable". - */ - function get_editable_roles() { - - global $wp_roles; - - $all_roles = $wp_roles->roles; - $editable_roles = apply_filters( 'editable_roles', $all_roles ); - - return $editable_roles; - } - - /** - * Cleans and validates form information before saving. - * - * @param array $input key-value information to be cleaned before saving. - * - * @return array The sanitized and valid data to be stored. - */ - public function sanitize_settings( $input ) { - - $sanitary_values = array(); - - $text_fields = array( - 'org_display_name', - 'org_domain_hint', - 'client_id', - 'client_secret', - 'redirect_uri', - 'logout_redirect_uri', - 'openid_configuration_endpoint', - ); - - foreach ($text_fields as $text_field) { - if ( isset( $input[ $text_field ] ) ) { - $sanitary_values[ $text_field ] = sanitize_text_field( $input[ $text_field ] ); - } - } - - // Default field_to_match_to_upn is 'email' - $sanitary_values['field_to_match_to_upn'] = 'email'; - if ( isset( $input['field_to_match_to_upn'] ) - && in_array( $input['field_to_match_to_upn'], array( 'email', 'login' ) ) - ) { - $sanitary_values['field_to_match_to_upn'] = $input['field_to_match_to_upn']; - } - - // Default role for user that is not member of any Azure AD group is null, which denies access. - $sanitary_values['default_wp_role'] = null; - if ( isset( $input['default_wp_role'] ) ) { - $sanitary_values['default_wp_role'] = sanitize_text_field( $input['default_wp_role'] ); - } - - // Booleans: when key == value, this is considered true, otherwise false. - $boolean_settings = array( - 'enable_auto_provisioning', - 'enable_auto_forward_to_aad', - 'enable_aad_group_to_wp_role', - 'match_on_upn_alias', - 'enable_full_logout', - ); - foreach ( $boolean_settings as $boolean_setting ) - { - if( isset( $input[ $boolean_setting ] ) ) { - $sanitary_values[ $boolean_setting ] = ( $boolean_setting == $input[ $boolean_setting ] ); - } else { - $sanitary_values[ $boolean_setting ] = false; - } - } - - /* - * Many of the roles in WordPress will not have Azure AD groups associated with them. - * Go over all roles, removing the mapping for empty ones. - */ - if ( isset( $input['role_map'] ) ) { - foreach( $input['role_map'] as $role_slug => $group_object_id ) { - if( empty( $group_object_id ) ) { - unset( $input['role_map'][ $role_slug ] ); - } - } - $sanitary_values['role_map'] = $input['role_map']; - } - - // If the OpenID Connect configuration endpoint is changed, clear the cached values. - $stored_oidc_config_endpoint = isset( $this->settings['openid_configuration_endpoint'] ) - ? $this->settings['openid_configuration_endpoint'] : null; - if ( $stored_oidc_config_endpoint !== $sanitary_values['openid_configuration_endpoint'] ) { - delete_transient( 'aadsso_openid_configuration' ); - AADSSO::debug_log('Setting \'openid_configuration_endpoint\' changed, cleared cached OpenID Connect values.'); - } - - return $sanitary_values; - } - - /** - * Renders details for the General settings section. - */ - public function settings_general_info() { } - - /** - * Renders details for the Advanced settings section. - */ - public function settings_advanced_info() { } - - /** - * Renders the `role_map` picker control. - */ - function role_map_callback() { - printf( '

%s

', - __( 'Map WordPress roles to Azure Active Directory groups.', 'aad-sso-wordpress' ) - ); - echo ''; - printf( - '', - __( 'WordPress Role', 'aad-sso-wordpress' ), - __( 'Azure AD Group Object ID', 'aad-sso-wordpress' ) - ); - echo ''; - foreach( $this->get_editable_roles( ) as $role_slug => $role ) { - echo ''; - echo ''; - echo ''; - echo ''; - } - echo ''; - echo '
%s%s
' . htmlentities( $role['name'] ) . ''; - printf( - '', - $role_slug, - isset( $this->settings['role_map'][ $role_slug ] ) - ? esc_attr( $this->settings['role_map'][ $role_slug ] ) - : '' - ); - echo '
'; - } - - /** - * Renders the `org_display_name` form control. - */ - public function org_display_name_callback() { - $this->render_text_field( 'org_display_name' ); - printf( - '

%s

', - __( 'Display Name will be shown on the WordPress login screen.', 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `org_domain_hint` form control. - */ - public function org_domain_hint_callback() { - $this->render_text_field( 'org_domain_hint' ); - printf( - '

%s

', - __( 'Provides a hint to Azure AD about the domain or tenant they will be logging in to. If ' - . 'the domain is federated, the user will be automatically redirected to federation ' - . 'endpoint.', 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `client_id` form control - */ - public function client_id_callback() { - $this->render_text_field( 'client_id' ); - printf( - '

%s

', - __( 'The client ID of the Azure AD application representing this blog.', 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `client_secret` form control - **/ - public function client_secret_callback() { - $this->render_text_field( 'client_secret' ); - printf( - '

%s

', - __( 'A secret key for the Azure AD application representing this blog.', 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `redirect_uri` form control - **/ - public function redirect_uri_callback() { - $this->render_text_field( 'redirect_uri' ); - printf( - ' %s' - . '

%s

', - wp_login_url(), - __( 'Set default', 'aad-sso-wordpress' ), - __( 'The URL where the user is redirected to after authenticating with Azure AD. ' - . 'This URL must be registered in Azure AD as a valid redirect URL, and it must be a ' - . 'page that invokes the "authenticate" filter. If you don\'t know what to set, leave ' - . 'the default value (which is this blog\'s login page).', 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `logout_redirect_uri` form control - **/ - public function logout_redirect_uri_callback() { - $this->render_text_field( 'logout_redirect_uri' ); - printf( - ' %s' - . '

%s

', - wp_login_url(), - __( 'Set default', 'aad-sso-wordpress'), - __( 'The URL where the user is redirected to after signing out of Azure AD. ' - . 'This URL must be registered in Azure AD as a valid redirect URL. (This does not affect ' - . ' logging out of the blog, it is only used when logging out of Azure AD.)', 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `field_to_match_to_upn` form control. - */ - public function field_to_match_to_upn_callback() { - $selected = - isset( $this->settings['field_to_match_to_upn'] ) - ? $this->settings['field_to_match_to_upn'] - : ''; - ?> - - %s

', - __( 'This specifies the WordPress user field which will be used to match to the Azure AD user\'s ' - . 'UserPrincipalName.', 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `match_on_upn_alias` checkbox control. - */ - public function match_on_upn_alias_callback() { - $this->render_checkbox_field( - 'match_on_upn_alias', - __( 'Match WordPress users based on the alias of their Azure AD UserPrincipalName. For example, ' - . 'Azure AD username bob@example.com will match WordPress user bob.', - 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `default_wp_role` control. - */ - public function default_wp_role_callback() { - - // Default configuration should be most-benign - if( ! isset( $this->settings['default_wp_role'] ) ) { - $this->settings['default_wp_role'] = ''; - } - - echo ''; - printf( - '

%s

', - __('This is the default role that users will be assigned to if matching Azure AD group to ' - . 'WordPress roles is enabled, but the signed in user isn\'t a member of any of the ' - . 'configured Azure AD groups.', 'aad-sso-wordpress') - ); - } - - /** - * Renders the `enable_auto_provisioning` checkbox control. - */ - public function enable_auto_provisioning_callback() { - $this->render_checkbox_field( - 'enable_auto_provisioning', - __( 'Automatically create WordPress users, if needed, for authenticated Azure AD users.', - 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `enable_auto_forward_to_aad` checkbox control. - */ - public function enable_auto_forward_to_aad_callback() { - $this->render_checkbox_field( - 'enable_auto_forward_to_aad', - __( 'Automatically forward users to the Azure AD to sign in, skipping the WordPress login screen.', - 'aad-sso-wordpress') - ); - } - - /** - * Renders the `enable_aad_group_to_wp_role` checkbox control. - */ - public function enable_aad_group_to_wp_role_callback() { - $this->render_checkbox_field( - 'enable_aad_group_to_wp_role', - __( 'Automatically assign WordPress user roles based on Azure AD group membership.', - 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `openid_configuration_endpoint` form control - **/ - public function openid_configuration_endpoint_callback() { - $this->render_text_field( 'openid_configuration_endpoint' ); - printf( - ' %s' - . '

%s

', - AADSSO_Settings::get_defaults( 'openid_configuration_endpoint' ), - __( 'Set default', 'aad-sso-wordpress'), - __( 'The OpenID Connect configuration endpoint to use. To support Microsoft Accounts and external ' - . 'users (users invited in from other Azure AD directories, known sometimes as "B2B users") you ' - . 'must use: https://login.microsoftonline.com/{tenant-id}/.well-known/openid-configuration, ' - . 'where {tenant-id} is the tenant ID or a verified domain name of your directory.', - 'aad-sso-wordpress' ) - ); - } - - /** - * Renders the `enable_full_logout` checkbox control. - */ - public function enable_full_logout_callback() { - $this->render_checkbox_field( - 'enable_full_logout', - __( 'Do a full logout of Azure AD when logging out of WordPress.', - 'aad-sso-wordpress' ) - ); - } - - /** - * Renders a simple text field and populates it with the setting value. - * - * @param string $name The setting name for the text input field. - */ - public function render_text_field( $name ) { - $value = isset( $this->settings[ $name ] ) ? esc_attr( $this->settings[ $name ] ) : ''; - printf( - '', - $name, $value - ); - } - - /** - * Renders a simple checkbox field and populates it with the setting value. - * - * @param string $name The setting name for the checkbox input field. - * @param string $label The label to use for the checkbox. - */ - public function render_checkbox_field( $name, $label ) { - printf( - '' - . '', - $name, - isset( $this->settings[ $name ] ) && $this->settings[ $name ] ? 'checked' : '', - $label - ); - } - - /** - * Indicates if user is currently on this settings page. - */ - public function is_on_options_page() { - $screen = get_current_screen(); - return $screen->id === $this->options_page_id; - } - - /** - * Ensures jQuery is loaded - */ - public function maybe_include_jquery() { - if ( $this->is_on_options_page() ) { - wp_enqueue_script( 'jquery' ); - } - } -} diff --git a/aad-sso-wordpress.php b/aad-sso-wordpress.php index 201476a..00dde5a 100644 --- a/aad-sso-wordpress.php +++ b/aad-sso-wordpress.php @@ -1,746 +1,108 @@ settings = $settings; - - // Setup the admin settings page - $this->setup_admin_settings(); - - // Some debugging locations - //add_action( 'admin_notices', array( $this, 'print_debug' ) ); - //add_action( 'login_footer', array( $this, 'print_debug' ) ); - - // Add a link to the Settings page in the list of plugins - add_filter( - 'plugin_action_links_' . plugin_basename( __FILE__ ), - array( $this, 'add_settings_link' ) - ); - - // Register activation and deactivation hooks - register_activation_hook( __FILE__, array( 'AADSSO', 'activate' ) ); - register_deactivation_hook( __FILE__, array( 'AADSSO', 'deactivate' ) ); - - // If plugin is not configured, we shouldn't proceed. - if ( ! $this->plugin_is_configured() ) { - add_action( 'all_admin_notices', array( $this, 'print_plugin_not_configured' ) ); - return; - } - - // Add the hook that starts the SESSION - add_action( 'login_init', array( $this, 'register_session' ), 10 ); - - // The authenticate filter - add_filter( 'authenticate', array( $this, 'authenticate' ), 1, 3 ); - - // Add the