array( 'reason' => $claims->get_error_code() ) ) ); return $claims; } Rate_Limiter::record( Rate_Limiter::ACTION_VERIFY, 'google', $ip, true ); $access = Access_Control::check( array( 'type' => 'email', 'value' => $claims['email'], ) ); if ( is_wp_error( $access ) ) { return $access; } $user = self::resolve_user( $claims ); if ( is_wp_error( $user ) ) { return $user; } return Flow::login( $user, 'google', $redirect_to ); } /** * Asks Google to validate a token and checks its claims. * * @param string $credential ID token. * @return array|WP_Error Claims: sub, email, name. */ public static function verify_token( $credential ) { $invalid = new WP_Error( 'aslansheed_google_invalid', __( 'Google sign-in could not be verified. Please try again.', 'aslansheed-login' ) ); if ( ! preg_match( '/^[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+$/', $credential ) || strlen( $credential ) > 4096 ) { return $invalid; } $response = Http::request( 'GET', add_query_arg( array( 'id_token' => $credential ), self::TOKEN_INFO_URL ) ); if ( ! $response['ok'] || ! is_array( $response['json'] ) ) { return $invalid; } $claims = $response['json']; $audience = isset( $claims['aud'] ) ? (string) $claims['aud'] : ''; $issuer = isset( $claims['iss'] ) ? (string) $claims['iss'] : ''; $expires = isset( $claims['exp'] ) ? (int) $claims['exp'] : 0; $verified = isset( $claims['email_verified'] ) && in_array( $claims['email_verified'], array( true, 'true', '1', 1 ), true ); if ( ! hash_equals( trim( (string) Options::get( 'google.client_id' ) ), $audience ) ) { return $invalid; } if ( ! in_array( $issuer, array( 'accounts.google.com', 'https://accounts.google.com' ), true ) || $expires < time() ) { return $invalid; } if ( empty( $claims['sub'] ) || empty( $claims['email'] ) || ! $verified || ! is_email( (string) $claims['email'] ) ) { return new WP_Error( 'aslansheed_google_email', __( 'Your Google account needs a verified email address.', 'aslansheed-login' ) ); } return array( 'sub' => (string) $claims['sub'], 'email' => strtolower( (string) $claims['email'] ), 'name' => isset( $claims['name'] ) ? (string) $claims['name'] : '', ); } /** * Finds, links or creates the user for verified claims. * * @param array $claims Claims from verify_token(). * @return \WP_User|WP_Error */ private static function resolve_user( array $claims ) { $identity = Identity_Store::find( self::IDENTITY_TYPE, $claims['sub'] ); if ( $identity && (int) $identity->user_id > 0 ) { $user = get_user_by( 'id', (int) $identity->user_id ); if ( $user ) { return $user; } } $parsed = array( 'type' => 'email', 'value' => $claims['email'], ); $user = get_user_by( 'email', $claims['email'] ); if ( $user ) { if ( ! Options::get( 'forms.google_link_by_email' ) ) { return new WP_Error( 'aslansheed_google_unlinked', __( 'An account with this email already exists. Sign in with another method first.', 'aslansheed-login' ) ); } Identity_Store::save( (int) $user->ID, self::IDENTITY_TYPE, $claims['sub'] ); Audit_Log::log( 'identity_linked', array( 'user_id' => (int) $user->ID, 'context' => array( 'type' => self::IDENTITY_TYPE ), ) ); return $user; } if ( ! Options::get( 'forms.allow_registration' ) ) { return new WP_Error( 'aslansheed_no_account', __( 'No account was found. Registration is closed.', 'aslansheed-login' ) ); } $user = User_Resolver::create( $parsed, array( 'name' => $claims['name'] ) ); if ( is_wp_error( $user ) ) { return $user; } Identity_Store::save( (int) $user->ID, self::IDENTITY_TYPE, $claims['sub'] ); Audit_Log::log( 'user_registered', array( 'user_id' => (int) $user->ID, 'identifier' => $claims['email'], 'context' => array( 'method' => 'google' ), ) ); return $user; } }