-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Login Process Error Handling #409
Conversation
@@ -144,12 +144,19 @@ public function login_auto() { | |||
} | |||
|
|||
public function init_auth0() { | |||
global $wp_query; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used
public function redirect_login() { | ||
global $wp_query; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used
public function redirect_login() { | ||
global $wp_query; | ||
|
||
if ( $this->query_vars( 'auth0' ) === null ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 3 if
blocks were moved to init_auth0()
to be caught earlier and for both redirect and implicit flows.
) ); | ||
|
||
if ( $response instanceof WP_Error ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Diff gets a little mangled from here down because of switching around how the errors are handled. This was removed in favor of checking a specific error code we're looking for, followed by a general check if we have the info we need (access_token
)
lib/WP_Auth0_LoginManager.php
Outdated
WP_Auth0_Api_Client::get_client_token(), | ||
$decoded_token->sub | ||
); | ||
// Unsuccessful for another reason |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Log the error if we've got something but don't show to end user
lib/WP_Auth0_LoginManager.php
Outdated
|
||
throw new WP_Auth0_LoginFlowValidationException( ); | ||
} | ||
if ( empty( $data->access_token ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only need the access token here
WP_Auth0_LoginManager was not processing errors well, was poorly documented, and might have been improperly exposing error messages. Incoming URL param errors from Auth0 and configuration issues are caught earlier in the login process. Error message are not exposed to the user; instead they are logged for an admin. Thrown errors are standarized and listed in docblocks. Fixes #305
325594a
to
46e20ec
Compare
$this->die_on_login( $e->getMessage(), $e->getCode(), FALSE ); | ||
|
||
} catch (Exception $e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blank general Exception
catching lead to issues like #305
|
||
$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 ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled earlier in init_auth0()
by WP_Auth0::ready()
lib/WP_Auth0_LoginManager.php
Outdated
|
||
$decoded_token = JWT::decode( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved down
lib/WP_Auth0_LoginManager.php
Outdated
|
||
$data->id_token = null; | ||
$response = WP_Auth0_Api_Client::get_user( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved down
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind of hard to read, but looks okay. Lot of handling cleaned up which is nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left some comments 👍
WP_Auth0.php
Outdated
@@ -123,6 +123,19 @@ public function init() { | |||
WP_Auth0_Email_Verification::init(); | |||
} | |||
|
|||
/** | |||
* Is the Auth0 plugin ready to proc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if "proc" is the short for something I didn't get it. To process, maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I just fell asleep in the middle of that docblock or something ...
WP_Auth0.php
Outdated
*/ | ||
public static function ready() { | ||
$options = WP_Auth0_Options::Instance(); | ||
if ( ! $options->get( 'domain' ) || ! $options->get( 'client_id' ) || ! $options->get( 'client_id' ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duplicated client_id
condition
@@ -240,9 +253,11 @@ public static function get_plugin_dir_url() { | |||
} | |||
|
|||
public function a0_register_query_vars( $qvars ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the purpose of this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It registers query variables (URL params) with the main WP_Query
instance that basically runs the whole site.
|
||
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() ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auth0
was the query param you use to track context, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, can be 1
for a "regular" login or implicit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. That's confusing, but as long as it's "internal" I don't care. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree but it's legacy and dangerous to unwire at this point. I'd like to just deprecate implicit altogether ...
// 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' ) ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the if
already checks if error_description
is present on the query. Is there any case where the sanitize_test_field(error_description)
will fail or give you something null/undefined? if not, it doesn't make sense to have this line's if
that calls sanitize_text_field(error)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll only ever get a scalar back from query_vars()
and sanitize_text_field()
is just there to sanitize. We're expecting error descriptions from the Auth0 server so if something comes through that's wonky then it's probably someone screwing around. If sanitize_text_field()
returns an empty value then it was empty to begin with or invalid symbols:
https://developer.wordpress.org/reference/functions/sanitize_text_field/
lib/WP_Auth0_LoginManager.php
Outdated
|
||
$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 ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest I think the "error handler" should catch the last 2 conditions already and create/raise a WP_ERROR. You should only be checking for the error existence, if not you are "extending" the error handler logic to every call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also a good call. If $userinfo_resp
is a WP_Error
then this will also be true (resolves the code to 0).
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 ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about
$redirectURL= $state_decoded->redirect_to
if ( empty( $redirectURL ) || wp_login_url() !== $redirectURL ) {
$redirectURL = $this->a0_options->get( 'default_login_redirection' );
}
wp_safe_redirect( $redirectURL );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if $state_decoded->redirect_to
is not set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know, I guess from your answer that it will throw an unhandled exception. If that's the case then fine 👍. I only suggested this as a way to avoid calling $state_decoded->redirect_to
3 times
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If $state_decoded->redirect_to
is not set and you try to access it (the exception being empty()
) then you get a notice. $redirectURL
ends up false-y and things progress fine.
"avoid calling $state_decoded->redirect_to
3 times" - just for the sake of style? Accessing a property like that shouldn't have any kind of performance impact.
lib/WP_Auth0_LoginManager.php
Outdated
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 ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's interim
? and what is the structure of the object state_decoded
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I'm not sure about interim
at the moment, has never come up so I left it in-tact.
State is an object with interim
(bool), nonce
(state validation), and state
(always set to "nonce" ... which can probably go). Sometimes redirect_to
is set to a URL but it can be blank.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joshcanhelp are you saying that state
is the string "nonce"
or that state==nonce
? In any case, remember this are individual values and should be different, secure random strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently (in JSON for clarity):
{
"interim" : true || false,
"nonce" : "2309u29j23o28j2je8",
"state" : "nonce"
}
* @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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK the AcT is always returned. The one that might be missing is the IdT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true ... but the $id_token
param was already there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So why isn't the access token provided "when using implicit_login()
"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're given an ID token via POST
which we use to ID the user. Access token isn't used for anything in that flow (at least in WP). Implicit in WP is a bit of a strange animal ...
* that the cookie will be kept. Default false. | ||
* } | ||
*/ | ||
// See wp_signon() for documentation on this filter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that's lazy 😛 I don't see it in this diff. Was this a copy of that method documentation? What's the reason behind removing this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really lazy, more like an unnecessary duplicate of WP core documentation. Seems silly to have that here and in the core location and try to keep the two updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair. 👍
@lbalmaceda - Fixes up and ready for re-review! |
lib/WP_Auth0_LoginManager.php
Outdated
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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there was an exit()
before right below this line. Are you sure it's no longer required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exit()
was moved to catch this and wp_safe_redirect()
(which requires an exit
/die
). Here
lib/WP_Auth0_LoginManager.php
Outdated
|
||
$userinfo_resp_code = (int) wp_remote_retrieve_response_code( $userinfo_resp ); | ||
$userinfo_resp_body = wp_remote_retrieve_body( $userinfo_resp ); | ||
|
||
if ( $userinfo_resp instanceof WP_Error || 200 !== $userinfo_resp_code || empty( $userinfo_resp_body ) ) { | ||
// Management API call failed | ||
if ( 200 !== $userinfo_resp_code || empty( $userinfo_resp_body ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant is remove this 2 conditions and only keep the $userinfo_resp instanceof WP_Error
one. Why did you choose to remove the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$userinfo_resp
is only a WP_Error
if something went wrong during the POST call. If it succeeds but you get a 401 or 404, the response code is what needs to be checked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
WP_Auth0_LoginManager was not processing errors well, was poorly
documented, and might have been improperly exposing error messages.
Incoming URL param errors from Auth0 and configuration issues are caught
earlier in the login process. Error message are not exposed to the
user; instead they are logged for an admin. Thrown errors are
standarized and listed in docblocks.
Fixes #305