diff --git a/WP_Auth0.php b/WP_Auth0.php index c01adb1c..b60270ee 100644 --- a/WP_Auth0.php +++ b/WP_Auth0.php @@ -123,6 +123,19 @@ public function init() { WP_Auth0_Email_Verification::init(); } + /** + * Is the Auth0 plugin ready to proc + * + * @return bool + */ + public static function ready() { + $options = WP_Auth0_Options::Instance(); + if ( ! $options->get( 'domain' ) || ! $options->get( 'client_id' ) || ! $options->get( 'client_id' ) ) { + return FALSE; + } + return TRUE; + } + /** * Checks it it should update the database connection no enable or disable signups and create or delete * the rule that will disable social signups. @@ -240,9 +253,11 @@ public static function get_plugin_dir_url() { } public function a0_register_query_vars( $qvars ) { + $qvars[] = 'error'; $qvars[] = 'error_description'; $qvars[] = 'a0_action'; $qvars[] = 'auth0'; + $qvars[] = 'state'; $qvars[] = 'code'; $qvars[] = 'state'; return $qvars; diff --git a/lib/WP_Auth0_LoginManager.php b/lib/WP_Auth0_LoginManager.php index c1ee0671..a40fa1a9 100755 --- a/lib/WP_Auth0_LoginManager.php +++ b/lib/WP_Auth0_LoginManager.php @@ -144,12 +144,19 @@ public function login_auto() { } public function init_auth0() { - global $wp_query; - if ( $this->query_vars( 'auth0' ) === null ) { + // Not an Auth0 login process or settings are not configured to allow logins + if ( ! $this->query_vars( 'auth0' ) || ! WP_Auth0::ready() ) { return; } + // Catch any incoming errors and stop the login process + // See https://auth0.com/docs/libraries/error-messages + if ( $this->query_vars( 'error_description' ) ) { + $error_msg = sanitize_text_field( $this->query_vars( 'error_description' ) ); + $this->die_on_login( $error_msg ? $error_msg : sanitize_text_field( $this->query_vars( 'error' ) ) ); + } + // Check for valid state nonce // See https://auth0.com/docs/protocols/oauth2/oauth-state $state_decoded = $this->get_state(); @@ -163,136 +170,124 @@ public function init_auth0() { } else { $this->redirect_login(); } - } catch (WP_Auth0_LoginFlowValidationException $e) { + } catch ( WP_Auth0_LoginFlowValidationException $e ) { + // Errors during the OAuth login flow $this->die_on_login( $e->getMessage(), $e->getCode() ); - } catch (WP_Auth0_BeforeLoginException $e) { + } catch ( WP_Auth0_BeforeLoginException $e ) { + // Errors during the WordPress login flow $this->die_on_login( $e->getMessage(), $e->getCode(), FALSE ); - - } catch (Exception $e) { - } - } + /** + * Main login flow, Authorization Code Grant + * + * @throws WP_Auth0_BeforeLoginException + * @throws WP_Auth0_LoginFlowValidationException + * + * @see https://auth0.com/docs/api-auth/tutorials/authorization-code-grant + */ public function redirect_login() { - global $wp_query; - - if ( $this->query_vars( 'auth0' ) === null ) { - return; - } - - if ( $this->query_vars( 'error_description' ) !== null && $this->query_vars( 'error_description' ) !== '' ) { - throw new WP_Auth0_LoginFlowValidationException( sanitize_text_field( $this->query_vars( 'error_description' ) ) ); - } - - if ( $this->query_vars( 'error' ) !== null && trim( $this->query_vars( 'error' ) ) !== '' ) { - throw new WP_Auth0_LoginFlowValidationException( sanitize_text_field( $this->query_vars( 'error' ) ) ); - } - - $code = $this->query_vars( 'code' ); - $state_decoded = $this->get_state(); $domain = $this->a0_options->get( 'domain' ); $client_id = $this->a0_options->get( 'client_id' ); $client_secret = $this->a0_options->get( 'client_secret' ); - if ( empty( $client_id ) ) { - throw new WP_Auth0_LoginFlowValidationException( __( 'Error: Your Auth0 Client ID has not been entered in the Auth0 SSO plugin settings.', 'wp-auth0' ) ); - } - if ( empty( $client_secret ) ) { - throw new WP_Auth0_LoginFlowValidationException( __( 'Error: Your Auth0 Client Secret has not been entered in the Auth0 SSO plugin settings.', 'wp-auth0' ) ); - } - if ( empty( $domain ) ) { - throw new WP_Auth0_LoginFlowValidationException( __( 'Error: No Domain defined in Wordpress Administration!', 'wp-auth0' ) ); - } - - $response = WP_Auth0_Api_Client::get_token( $domain, $client_id, $client_secret, 'authorization_code', array( + // Exchange authorization code for token + $exchange_resp = WP_Auth0_Api_Client::get_token( $domain, $client_id, $client_secret, 'authorization_code', array( 'redirect_uri' => home_url(), - 'code' => $code, + 'code' => $this->query_vars( 'code' ), ) ); - if ( $response instanceof WP_Error ) { - WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => WP_Auth0_Api_Client::get_token()', $response ); - - error_log( $response->get_error_message() ); + $exchange_resp_code = (int) wp_remote_retrieve_response_code( $exchange_resp ); + $exchange_resp_body = wp_remote_retrieve_body( $exchange_resp ); - throw new WP_Auth0_LoginFlowValidationException( $response->get_error_message() ); - } - - $data = json_decode( $response['body'] ); + if ( 401 === $exchange_resp_code ) { - if ( isset( $data->access_token ) || isset( $data->id_token ) ) { + // Not authorized + WP_Auth0_ErrorManager::insert_auth0_error( + __METHOD__ . ' L:' . __LINE__, + __( 'An oauth/token call triggered a 401 response from Auth0. ', 'wp-auth0' ) . + __( 'Please check the Client Secret saved in the Auth0 plugin settings. ', 'wp-auth0' ) + ); + throw new WP_Auth0_LoginFlowValidationException( __( 'Not Authorized', 'wp-auth0' ), $exchange_resp_code ); - $decoded_token = JWT::decode( - $data->id_token, - $this->a0_options->get_client_secret_as_key(), - array( $this->a0_options->get_client_signing_algorithm() ) - ); + } else if ( empty( $exchange_resp_body ) ) { - $data->id_token = null; - $response = WP_Auth0_Api_Client::get_user( - $this->a0_options->get( 'domain' ), - WP_Auth0_Api_Client::get_client_token(), - $decoded_token->sub - ); + // Unsuccessful for another reason + if ( $exchange_resp instanceof WP_Error ) { + WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' L:' . __LINE__, $exchange_resp ); + } - if ( $response instanceof WP_Error ) { - WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => WP_Auth0_Api_Client::get_user()', $response ); + throw new WP_Auth0_LoginFlowValidationException( __( 'Unknown error', 'wp-auth0' ), $exchange_resp_code ); + } - error_log( $response->get_error_message() ); + $data = json_decode( $exchange_resp_body ); - throw new WP_Auth0_LoginFlowValidationException( ); - } + if ( empty( $data->access_token ) ) { - $userinfo = json_decode( $response['body'] ); - if ( $this->login_user( $userinfo, $data->id_token, $data->access_token ) ) { - if ( ! empty( $state_decoded->interim ) ) { - include WPA0_PLUGIN_DIR . 'templates/login-interim.php'; - exit(); - } else { - if ( ! empty( $state_decoded->redirect_to ) && wp_login_url() !== $state_decoded->redirect_to ) { - $redirectURL = $state_decoded->redirect_to; - } else { - $redirectURL = $this->a0_options->get( 'default_login_redirection' ); - } + // Look for clues as to what went wrong + throw new WP_Auth0_LoginFlowValidationException( + ! empty( $data->error_description ) ? $data->error_description : __( 'Unknown error', 'wp-auth0' ), + ( ! empty( $data->error ) ? $data->error : $exchange_resp_code ) + ); + } - wp_safe_redirect( $redirectURL ); - } - } - } elseif ( is_array( $response['response'] ) && 401 === (int) $response['response']['code'] ) { + $decoded_token = JWT::decode( + $data->id_token, + $this->a0_options->get_client_secret_as_key(), + array( $this->a0_options->get_client_signing_algorithm() ) + ); - $error = new WP_Error( '401', 'auth/token response code: 401 Unauthorized' ); + $userinfo_resp = WP_Auth0_Api_Client::get_user( + $this->a0_options->get( 'domain' ), + WP_Auth0_Api_Client::get_client_token(), + $decoded_token->sub + ); - WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => $this->login_user() = 401', $error ); + $userinfo_resp_code = (int) wp_remote_retrieve_response_code( $userinfo_resp ); + $userinfo_resp_body = wp_remote_retrieve_body( $userinfo_resp ); - $msg = __( 'Error: the Client Secret configured on the Auth0 plugin is wrong. Make sure to copy the right one from the Auth0 dashboard.', 'wp-auth0' ); + if ( $userinfo_resp instanceof WP_Error || 200 !== $userinfo_resp_code || empty( $userinfo_resp_body ) ) { - throw new WP_Auth0_LoginFlowValidationException( $msg ); - } else { - $error = ''; - $description = ''; + // Management API call failed + // TODO: fallback to /userinfo + WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => WP_Auth0_Api_Client::get_user()', $userinfo_resp ); + error_log( $userinfo_resp->get_error_message() ); + throw new WP_Auth0_LoginFlowValidationException( __( 'Error getting user information', 'wp-auth0' ) ); + } - if ( isset( $data->error ) ) { - $error = $data->error; - } - if ( isset( $data->error_description ) ) { - $description = $data->error_description; - } + $userinfo = json_decode( $userinfo_resp_body ); - if ( ! empty( $error ) || ! empty( $description ) ) { - $error = new WP_Error( $error, $description ); - WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => $this->login_user()', $error ); + if ( $this->login_user( $userinfo, $data->id_token, $data->access_token ) ) { + $state_decoded = $this->get_state(); + if ( ! empty( $state_decoded->interim ) ) { + include WPA0_PLUGIN_DIR . 'templates/login-interim.php'; + } else { + if ( ! empty( $state_decoded->redirect_to ) && wp_login_url() !== $state_decoded->redirect_to ) { + $redirectURL = $state_decoded->redirect_to; + } else { + $redirectURL = $this->a0_options->get( 'default_login_redirection' ); + } + wp_safe_redirect( $redirectURL ); } - // Login failed! - wp_redirect( home_url() . '?message=' . $data->error_description ); + exit(); } - exit(); } + /** + * Secondary login flow, Implicit Grant + * Client should be of type "Single Page App" for this flow + * + * @throws WP_Auth0_BeforeLoginException + * @throws WP_Auth0_LoginFlowValidationException + * + * @see https://auth0.com/docs/api-auth/tutorials/implicit-grant + */ public function implicit_login() { $token = $_POST['token']; @@ -306,7 +301,7 @@ public function implicit_login() { // validate that this JWT was made for us if ( $this->a0_options->get( 'client_id' ) !== $decodedToken->aud ) { - throw new Exception( 'This token is not intended for us.' ); + throw new WP_Auth0_LoginFlowValidationException( 'This token is not intended for us.' ); } $decodedToken->user_id = $decodedToken->sub; @@ -336,12 +331,17 @@ public function implicit_login() { } } - // Does all actions required to log the user in to wordpress, invoking hooks as necessary - // $user (stdClass): the WP user object, such as returned by get_user_by(...) - // $user_profile (stdClass): the Auth0 profile of the user - // $is_new (boolean): `true` if the user was created on Wordress, `false` if not. Don't get confused with Auth0 registrations, this flag will tell you if a new user was created on the WordPress database. - // $id_token (string): the user's JWT - // $access_token (string): the user's access token. It is not provided when using the **Implicit flow**. + /** + * Does all actions required to log the user in to wordpress, invoking hooks as necessary + * + * @param object $user - the WP user object, such as returned by get_user_by() + * @param object $userinfo - the Auth0 profile of the user + * @param bool $is_new - `true` if the user was created in the WordPress database, `false` if not + * @param string $id_token - user's ID token returned from Auth0 + * @param string $access_token - user's access token returned from Auth0; not provided when using implicit_login() + * + * @throws WP_Auth0_BeforeLoginException + */ private function do_login( $user, $userinfo, $is_new, $id_token, $access_token ) { $remember_users_session = $this->a0_options->get( 'remember_users_session' ); @@ -356,21 +356,7 @@ private function do_login( $user, $userinfo, $is_new, $id_token, $access_token ) $secure_cookie = is_ssl(); - /** - * Filters whether to use a secure sign-on cookie. - * - * @since 3.1.0 - * - * @param bool $secure_cookie Whether to use a secure sign-on cookie. - * @param array $credentials { - * Array of entered sign-on data. - * - * @type string $user_login Username. - * @type string $user_password Password entered. - * @type bool $remember Whether to 'remember' the user. Increases the time - * that the cookie will be kept. Default false. - * } - */ + // See wp_signon() for documentation on this filter $secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, array( "user_login" => $user->user_login, "user_password" => null, @@ -378,13 +364,21 @@ private function do_login( $user, $userinfo, $is_new, $id_token, $access_token ) ) ); - //wp_set_current_user( $user->ID, $user->user_login ); wp_set_auth_cookie( $user->ID, $remember_users_session, $secure_cookie); do_action( 'wp_login', $user->user_login, $user ); do_action( 'auth0_user_login' , $user->ID, $userinfo, $is_new, $id_token, $access_token ); } - // return true if login was successful, false otherwise + /** + * @param object $userinfo - the Auth0 profile of the user + * @param string $id_token - user's ID token returned from Auth0 + * @param string $access_token - user's access token returned from Auth0; not provided when using implicit_login() + * + * @return bool + * + * @throws WP_Auth0_BeforeLoginException + * @throws WP_Auth0_LoginFlowValidationException + */ public function login_user( $userinfo, $id_token, $access_token ) { // If the userinfo has no email or an unverified email, and in the options we require a verified email // notify the user he cant login until he does so. @@ -473,10 +467,6 @@ public function login_user( $userinfo, $id_token, $access_token ) { } catch ( WP_Auth0_EmailNotVerifiedException $e ) { WP_Auth0_Email_Verification::render_die( $e->userinfo ); } - // catch ( Exception $e ) { - // echo $e;exit; - // } - return true; } }