From 0bbfc3437f157c2954e47f088bc3d0877db79f5a Mon Sep 17 00:00:00 2001 From: Moriyoshi Koizumi Date: Wed, 15 Mar 2017 03:49:56 +0000 Subject: [PATCH] More flexible identity settings. --- .../openid-connect-generic-client-wrapper.php | 180 +++++++++++++++--- .../openid-connect-generic-settings-page.php | 29 ++- openid-connect-generic.php | 6 +- 3 files changed, 191 insertions(+), 24 deletions(-) diff --git a/includes/openid-connect-generic-client-wrapper.php b/includes/openid-connect-generic-client-wrapper.php index dd18d29..03c47f4 100644 --- a/includes/openid-connect-generic-client-wrapper.php +++ b/includes/openid-connect-generic-client-wrapper.php @@ -367,6 +367,9 @@ class OpenID_Connect_Generic_Client_Wrapper { // if we didn't find an existing user, we'll need to create it if ( ! $user ) { $user = $this->create_new_user( $subject_identity, $user_claim ); + if ( is_wp_error( $user ) ) { + return $this->error_redirect( $user ); + } } else { // allow plugins / themes to take action using current claims on existing user (e.g. update role) @@ -579,7 +582,81 @@ class OpenID_Connect_Generic_Client_Wrapper { return $username; } - + + /** + * Get a nickname + * + * @param $user_claim array + * + * @return string + */ + private function get_nickname_from_claim( $user_claim ) { + $desired_nickname = null; + // allow settings to take first stab at nickname + if ( !empty( $this->settings->nickname_key ) && isset( $user_claim[ $this->settings->nickname_key ] ) ) { + $desired_nickname = $user_claim[ $this->settings->nickname_key ]; + } + return $desired_nickname; + } + + /** + * Build a string from the user claim according to the specified format. + * + * @param $format string + * @param $user_claim array + * + * @return string + */ + private function format_string_with_claim( $format, $user_claim, $error_on_missing_key = false ) { + $matches = null; + $string = ''; + $i = 0; + if ( preg_match_all( '/\{[^}]*\}/u', $format, $matches, PREG_OFFSET_CAPTURE ) ) { + foreach ( $matches[ 0 ] as $match ) { + $key = substr($match[ 0 ], 1, -1); + $string .= substr( $format, $i, $match[ 1 ] - $i ); + if ( ! isset( $user_claim[ $key ] ) ) { + if ( $error_on_missing_key ) { + return new WP_Error( 'incomplete-user-claim', __( 'User claim incomplete' ), $user_claim ); + } + } else { + $string .= $user_claim[ $key ]; + } + $i = $match[ 1 ] + strlen( $match[ 0 ] ); + } + } + $string .= substr( $format, $i ); + return $string; + } + + /** + * Get a displayname + * + * @param $user_claim array + * + * @return string + */ + private function get_displayname_from_claim( $user_claim, $error_on_missing_key = false ) { + if ( ! empty( $this->settings->displayname_format ) ) { + return $this->format_string_with_claim( $this->settings->displayname_format, $user_claim, $error_on_missing_key ); + } + return null; + } + + /** + * Get an email + * + * @param $user_claim array + * + * @return string + */ + private function get_email_from_claim( $user_claim, $error_on_missing_key = false ) { + if ( ! empty( $this->settings->email_format ) ) { + return $this->format_string_with_claim( $this->settings->email_format, $user_claim, $error_on_missing_key ); + } + return null; + } + /** * Create a new user from details in a user_claim * @@ -588,22 +665,46 @@ class OpenID_Connect_Generic_Client_Wrapper { * * @return \WP_Error | \WP_User */ - function create_new_user( $subject_identity, $user_claim){ + function create_new_user( $subject_identity, $user_claim ) { // default username & email to the subject identity $username = $subject_identity; $email = $subject_identity; + $nickname = $subject_identity; + $displayname = $subject_identity; - // allow claim details to determine username - if ( isset( $user_claim['email'] ) ) { - $email = $user_claim['email']; - $username = $this->get_username_from_claim( $user_claim ); - - if ( is_wp_error( $username ) ){ - return $username; - } + $values_missing = false; + + // allow claim details to determine username, email, nickname and displayname. + $_email = $this->get_email_from_claim( $user_claim, true ); + if ( is_wp_error( $_email ) ) { + $values_missing = true; + } else if ( $_email !== null ) { + $email = $_email; + } + + $_username = $this->get_username_from_claim( $user_claim ); + if ( is_wp_error( $_username ) ) { + $values_missing = true; + } else if ( $_username !== null ) { + $username = $_username; + } + + $_nickname = $this->get_nickname_from_claim( $user_claim, true ); + if ( is_wp_error( $_nickname ) ) { + $values_missing = true; + } else if ( $_nickname !== null) { + $nickname = $_nickname; } - // if no email exists, attempt another request for userinfo - else if ( isset( $token_response['access_token'] ) ) { + + $_displayname = $this->get_displayname_from_claim( $user_claim, true ); + if ( is_wp_error( $_displayname ) ) { + $values_missing = true; + } else if ( $_displayname !== null ) { + $displayname = $_displayname; + } + + // attempt another request for userinfo if some values are missing + if ( $values_missing && isset( $token_response['access_token'] ) ) { $user_claim_result = $this->client->request_userinfo( $token_response['access_token'] ); // make sure we didn't get an error @@ -612,23 +713,48 @@ class OpenID_Connect_Generic_Client_Wrapper { } $user_claim = json_decode( $user_claim_result['body'], TRUE ); + } - // check for email in claim - if ( ! isset( $user_claim['email'] ) ) { - return new WP_Error( 'incomplete-user-claim', __( 'User claim incomplete' ), $user_claim ); - } - - $email = $user_claim['email']; - $username = $this->get_username_from_claim( $user_claim ); + $_email = $this->get_email_from_claim( $user_claim, true ); + if ( is_wp_error( $_email ) ) { + return $_email; + } else if ( $_email !== null ) { + $email = $_email; + } + + $_username = $this->get_username_from_claim( $user_claim ); + if ( is_wp_error( $_username ) ) { + return $_username; + } else if ( $_username !== null ) { + $username = $_username; + } + + $_nickname = $this->get_nickname_from_claim( $user_claim, true ); + if ( is_wp_error( $_nickname ) ) { + return $_nickname; + } else if ( $_nickname === null) { + $nickname = $username; + } + + $_displayname = $this->get_displayname_from_claim( $user_claim, true ); + if ( is_wp_error( $_displayname ) ) { + return $_displayname; + } else if ( $_displayname === null ) { + $displayname = $nickname; } // before trying to create the user, first check if a user with the same email already exists if( $this->settings->link_existing_users ) { - if( $uid = email_exists( $email ) ) { + if ( $this->settings->identify_with_username) { + $uid = username_exists( $username ); + } else { + $uid = email_exists( $email ); + } + if ( $uid ) { return $this->update_existing_user( $uid, $subject_identity ); } } - + // allow other plugins / themes to determine authorization // of new accounts based on the returned user claim $create_user = apply_filters( 'openid-connect-generic-user-creation-test', TRUE, $user_claim ); @@ -638,7 +764,17 @@ class OpenID_Connect_Generic_Client_Wrapper { } // create the new user - $uid = wp_create_user( $username, wp_generate_password( 32, TRUE, TRUE ), $email ); + $uid = wp_insert_user( + array( + 'user_login' => $username, + 'user_pass' => wp_generate_password( 32, TRUE, TRUE ), + 'user_email' => $email, + 'display_name' => $displayname, + 'nickname' => $nickname, + 'first_name' => isset( $user_claim[ 'given_name' ] ) ? $user_claim[ 'given_name' ]: '', + 'last_name' => isset( $user_claim[ 'family_name' ] ) ? $user_claim[ 'family_name' ]: '', + ) + ); // make sure we didn't fail in creating the user if ( is_wp_error( $uid ) ) { diff --git a/includes/openid-connect-generic-settings-page.php b/includes/openid-connect-generic-settings-page.php index f3aa192..52716ff 100644 --- a/includes/openid-connect-generic-settings-page.php +++ b/includes/openid-connect-generic-settings-page.php @@ -124,9 +124,36 @@ class OpenID_Connect_Generic_Settings_Page { 'type' => 'checkbox', 'section' => 'authorization_settings', ), + 'nickname_key' => array( + 'title' => __( 'Nickname Key' ), + 'description' => __( 'Where in the user claim array to find the user\'s nickname. Possible standard values: preferred_username, name, or sub.' ), + 'example' => 'preferred_username', + 'type' => 'text', + 'section' => 'client_settings', + ), + 'email_format' => array( + 'title' => __( 'Email Formatting' ), + 'description' => __( 'String from which the user\'s email address is built. Specify "{email}" as long as the user claim contains an email claim.' ), + 'example' => '{email}', + 'type' => 'text', + 'section' => 'client_settings', + ), + 'displayname_format' => array( + 'title' => __( 'Display Name Formatting' ), + 'description' => __( 'String from which the user\'s display name is built.' ), + 'example' => '{given_name} {family_name}', + 'type' => 'text', + 'section' => 'client_settings', + ), + 'identify_with_username' => array( + 'title' => __( 'Identify with User Name' ), + 'description' => __( 'If checked, the user\' identity will be determined by the user name instead of the email address.' ), + 'type' => 'checkbox', + 'section' => 'client_settings', + ), 'link_existing_users' => array( 'title' => __( 'Link Existing Users' ), - 'description' => __( 'If a WordPress account already exists with the same email address as a newly-authenticated user over OpenID Connect, login as that user instead of generating an error.' ), + 'description' => __( 'If a WordPress account already exists with the same identity as a newly-authenticated user over OpenID Connect, login as that user instead of generating an error.' ), 'type' => 'checkbox', 'section' => 'user_settings', ), diff --git a/openid-connect-generic.php b/openid-connect-generic.php index ed01f60..d6437d3 100644 --- a/openid-connect-generic.php +++ b/openid-connect-generic.php @@ -196,7 +196,11 @@ class OpenID_Connect_Generic { 'no_sslverify' => 0, 'http_request_timeout' => 5, 'identity_key' => 'preferred_username', - + 'nickname_key' => 'preferred_username', + 'email_format' => '{email}', + 'displayname_format' => '', + 'identify_with_username' => false, + // plugin settings 'enforce_privacy' => 0, 'alternate_redirect_uri' => 0,