Skip to content

Commit

Permalink
Add back JTI checking and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joshcanhelp committed Dec 14, 2018
1 parent 3e7d743 commit 72385a1
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 64 deletions.
54 changes: 41 additions & 13 deletions lib/WP_Auth0_Routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,14 @@ protected function migration_ws_login() {
}
}

$authorization = trim( str_replace( 'Bearer ', '', $this->getAuthorizationHeader() ) );
$migration_token = $this->a0_options->get( 'migration_token' );

try {
$authorization = trim( str_replace( 'Bearer ', '', $this->getAuthorizationHeader() ) );

if ( empty( $authorization ) ) {
throw new Exception( __( 'Unauthorized: missing authorization header', 'wp-auth0' ), 401 );
}

if ( $authorization !== $migration_token ) {
if ( ! $this->valid_token( $authorization ) ) {
throw new Exception( __( 'Invalid token', 'wp-auth0' ), 401 );
}

Expand Down Expand Up @@ -230,17 +229,15 @@ protected function migration_ws_get_user() {
}
}

$authorization = trim( str_replace( 'Bearer ', '', $this->getAuthorizationHeader() ) );
$migration_token = $this->a0_options->get( 'migration_token' );

$user = null;

try {
$authorization = trim( str_replace( 'Bearer ', '', $this->getAuthorizationHeader() ) );
$user = null;

if ( empty( $authorization ) ) {
throw new Exception( __( 'Unauthorized: missing authorization header', 'wp-auth0' ), 401 );
}

if ( $authorization !== $migration_token ) {
if ( ! $this->valid_token( $authorization ) ) {
throw new Exception( __( 'Invalid token', 'wp-auth0' ), 401 );
}

Expand Down Expand Up @@ -294,19 +291,50 @@ protected function oauth2_config() {
* @return array
*/
private function error_return_array( $code ) {

$error = array(
'status' => 500,
'error' => __( 'Internal Server Error', 'wp-auth0' ),
);
switch ( $code ) {
case 401:
return array(
$error = array(
'status' => 401,
'error' => __( 'Unauthorized', 'wp-auth0' ),
);
break;

case 403:
return array(
$error = array(
'status' => 403,
'error' => __( 'Forbidden', 'wp-auth0' ),
);
break;
}
return $error;
}

/**
* Check if a token or token JTI is the same as what is stored.
*
* @param string $authorization - Incoming migration token;
*
* @return bool
*/
private function valid_token( $authorization ) {
$token = $this->a0_options->get( 'migration_token' );
if ( $token === $authorization ) {
return true;
}
$client_secret = $this->a0_options->get( 'client_secret' );
if ( $this->a0_options->get( 'client_secret_base64_encoded' ) ) {
$client_secret = JWT::urlsafeB64Decode( $client_secret );
}

try {
$decoded = JWT::decode( $token, $client_secret, array( 'HS256' ) );
return isset( $decoded->jti ) && $decoded->jti === $this->a0_options->get( 'migration_token_id' );
} catch ( Exception $e ) {
return false;
}
}
}
50 changes: 25 additions & 25 deletions lib/admin/WP_Auth0_Admin_Advanced.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function init() {
'function' => 'render_auto_provisioning',
),
array(
'name' => __( 'User Migration', 'wp-auth0' ),
'name' => __( 'User Migration Endpoints', 'wp-auth0' ),
'opt' => 'migration_ws',
'id' => 'wpa0_migration_ws',
'function' => 'render_migration_ws',
Expand Down Expand Up @@ -375,9 +375,10 @@ public function render_migration_ws( $args = array() ) {

if ( $value ) {
$this->render_field_description(
__( 'Users migration is enabled. ', 'wp-auth0' ) .
__( 'If you disable this setting, it must be re-enabled manually in the ', 'wp-auth0' ) .
$this->get_dashboard_link()
__( 'User migration endpoints activated. ', 'wp-auth0' ) .
__( 'See below for the token to use. ', 'wp-auth0' ) .
__( 'The custom database scripts need to be configured manually as described ', 'wp-auth0' ) .
$this->get_docs_link( 'users/migrations/automatic' )
);
$this->render_field_description( 'Security token:' );
if ( $this->options->has_constant_val( 'migration_token' ) ) {
Expand All @@ -389,9 +390,9 @@ public function render_migration_ws( $args = array() ) {
);
} else {
$this->render_field_description(
__( 'Users migration is disabled. ', 'wp-auth0' ) .
__( 'Enabling this exposes migration webservices but the Connection must be updated manually. ', 'wp-auth0' ) .
$this->get_docs_link( 'users/migrations/automatic', __( 'More information here', 'wp-auth0' ) )
__( 'User migration endpoints deactivated. ', 'wp-auth0' ) .
__( 'Custom database connections can be deactivated in the ', 'wp-auth0' ) .
$this->get_dashboard_link( 'connections/database' )
);
}
}
Expand Down Expand Up @@ -717,37 +718,36 @@ public function basic_validation( $old_options, $input ) {
public function migration_ws_validation( array $old_options, array $input ) {
$input['migration_ws'] = isset( $input['migration_ws'] ) ? $input['migration_ws'] : 0;

// No longer using the token ID for validation.
$input['migration_token_id'] = null;

// No change to migration endpoints, keep old token data.
if ( $old_options['migration_ws'] === $input['migration_ws'] ) {
$input['migration_token'] = $old_options['migration_token'];
$input['migration_token'] = $old_options['migration_token'];
$input['migration_token_id'] = $old_options['migration_token_id'];
return $input;
}

// Migration endpoints turned off; warn admin of implications.
// Migration endpoints turned off.
if ( empty( $input['migration_ws'] ) ) {
$input['migration_token'] = null;

$error = __( 'User migration endpoints deactivated. ', 'wp-auth0' );
$error .= __( 'Custom database connections can be deactivated in the ', 'wp-auth0' );
$error .= $this->get_dashboard_link( 'connections/database' );
$this->add_validation_error( $error, 'updated' );
return $input;
}

// If we don't have a token yet, generate one.
$input['migration_token_id'] = null;
if ( empty( $input['migration_token'] ) ) {
// If we don't have a token yet, generate one.
$input['migration_token'] = base64_encode( openssl_random_pseudo_bytes( 64 ) );
} else {
// If we do have one, try to decode and store the JTI.
$secret = $input['client_secret'];
if ( ! empty( $input['client_secret_b64_encoded'] ) ) {
$secret = base64_decode( $input['client_secret'] );
}
try {
$token_decoded = JWT::decode( $input['migration_token'], $secret, array( 'HS256' ) );
$input['migration_token_id'] = isset( $token_decoded->jti ) ? $token_decoded->jti : null;
} catch ( Exception $e ) {
echo "\n\n{$e->getMessage()}\n\n";
}
}

$error = __( 'User migration endpoints activated. ', 'wp-auth0' );
$error .= __( 'The custom database scripts needs to be configured manually as described ', 'wp-auth0' );
$error .= $this->get_docs_link( 'users/migrations/automatic' ) . '. ';
$error .= __( 'Please see Advanced > Users Migration below for the token to use.', 'wp-auth0' );
$this->add_validation_error( $error, 'updated' );

$this->router->setup_rewrites();
flush_rewrite_rules();

Expand Down
2 changes: 1 addition & 1 deletion lib/admin/WP_Auth0_Admin_Generic.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ protected function render_radio_buttons( array $buttons, $id, $input_name, $curr
* @param string $text - description text to display
*/
protected function render_field_description( $text ) {
$period = ! in_array( $text[ strlen( $text ) - 1 ], array( '.', ':', '>' ) ) ? '.' : '';
$period = ! in_array( $text[ strlen( $text ) - 1 ], array( '.', ':' ) ) ? '.' : '';
printf( '<div class="subelement"><span class="description">%s%s</span></div>', $text, $period );
}

Expand Down
74 changes: 50 additions & 24 deletions tests/testOptionMigrationWs.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,41 +86,38 @@ public function testThatMigrationNotChangingKeepsOldTokenData() {
$validated = self::$admin->migration_ws_validation( $old_input, $input );
$this->assertEquals( $old_input['migration_ws'], $validated['migration_ws'] );
$this->assertEquals( $old_input['migration_token'], $validated['migration_token'] );
$this->assertNull( $validated['migration_token_id'] );
}

/**
* Test that turning migration endpoints off will clear out the token and set an admin notice.
*/
public function testThatChangingMigrationToOffClearsTokensSetsError() {
public function testThatChangingMigrationToOffClearsTokens() {
$input = [
'migration_ws' => 0,
'migration_token' => 'new_token',
'migration_ws' => 0,
'migration_token' => 'new_token',
'migration_token_id' => 'new_token_id',
];
$old_input = [
'migration_ws' => 1,
'migration_token' => 'old_token',
'migration_token_id' => 'old_token_id',
];
$old_input = [ 'migration_ws' => 1 ];

$validated = self::$admin->migration_ws_validation( $old_input, $input );

$this->assertNull( $validated['migration_token'] );
$this->assertNull( $validated['migration_token_id'] );
$this->assertEquals( $input['migration_ws'], $validated['migration_ws'] );

$errors = get_settings_errors();
$this->assertEquals( 'wp_auth0_settings', $errors[0]['setting'] );
$this->assertEquals( 'wp_auth0_settings', $errors[0]['code'] );
$this->assertEquals( 'updated', $errors[0]['type'] );
$this->assertContains( 'User migration endpoints deactivated', $errors[0]['message'] );
$this->assertContains( 'Custom database connections can be deactivated in the', $errors[0]['message'] );
$this->assertContains( 'https://manage.auth0.com/#/connections/database', $errors[0]['message'] );
$this->assertEquals( $input['migration_token'], $validated['migration_token'] );
$this->assertEquals( $input['migration_token_id'], $validated['migration_token_id'] );
}

/**
* Test that turning on migration keeps the existing token and sets an admin notification.
*/
public function testThatChangingMigrationToOnKeepsTokenSetsError() {
public function testThatChangingMigrationToOnKeepsToken() {
$input = [
'migration_ws' => 1,
'migration_token' => 'new_token',
'client_secret' => '__test_client_secret__',
];
$old_input = [ 'migration_ws' => 0 ];

Expand All @@ -129,15 +126,43 @@ public function testThatChangingMigrationToOnKeepsTokenSetsError() {
$this->assertEquals( $input['migration_token'], $validated['migration_token'] );
$this->assertNull( $validated['migration_token_id'] );
$this->assertEquals( $input['migration_ws'], $validated['migration_ws'] );
}

/**
* Test that turning on migration keeps the existing token and sets an admin notification.
*/
public function testThatChangingMigrationToOnKeepsWithJwtSetsId() {
$client_secret = '__test_client_secret__';
$input = [
'migration_ws' => 1,
'migration_token' => JWT::encode( [ 'jti' => '__test_token_id__' ], $client_secret ),
'client_secret' => $client_secret,
];
$old_input = [ 'migration_ws' => 0 ];

$validated = self::$admin->migration_ws_validation( $old_input, $input );

$this->assertEquals( $input['migration_ws'], $validated['migration_ws'] );
$this->assertEquals( $input['migration_token'], $validated['migration_token'] );
$this->assertEquals( '__test_token_id__', $validated['migration_token_id'] );
}

/**
* Test that turning on migration keeps the existing token and sets an admin notification.
*/
public function testThatChangingMigrationToOnKeepsWithBase64JwtSetsId() {
$client_secret = '__test_client_secret__';
$input = [
'migration_ws' => 1,
'migration_token' => JWT::encode( [ 'jti' => '__test_token_id__' ], $client_secret ),
'client_secret' => JWT::urlsafeB64Encode( $client_secret ),
'client_secret_b64_encoded' => 1,
];
$old_input = [ 'migration_ws' => 0 ];

$validated = self::$admin->migration_ws_validation( $old_input, $input );

$errors = get_settings_errors();
$this->assertEquals( 'wp_auth0_settings', $errors[0]['setting'] );
$this->assertEquals( 'wp_auth0_settings', $errors[0]['code'] );
$this->assertEquals( 'updated', $errors[0]['type'] );
$this->assertContains( 'User migration endpoints activated', $errors[0]['message'] );
$this->assertContains( 'The custom database scripts needs to be configured manually', $errors[0]['message'] );
$this->assertContains( 'https://auth0.com/docs/users/migrations/automatic', $errors[0]['message'] );
$this->assertContains( 'Please see Advanced > Users Migration below for the token to use', $errors[0]['message'] );
$this->assertEquals( '__test_token_id__', $validated['migration_token_id'] );
}

/**
Expand All @@ -164,6 +189,7 @@ public function testThatMigrationTokenInConstantSettingIsValidated() {
$input = [
'migration_ws' => 1,
'migration_token' => AUTH0_ENV_MIGRATION_TOKEN,
'client_secret' => '__test_client_secret__',
];
$old_input = [ 'migration_ws' => 0 ];

Expand Down
8 changes: 7 additions & 1 deletion tests/testRoutesGetUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,13 @@ public function testThatGetUserRouteReturnsUserIfSuccessful() {
$token_id = '__test_token_id__';
$_POST['username'] = uniqid() . '@' . uniqid() . '.com';
$user = $this->createUser( [ 'user_email' => $_POST['username'] ] );
$migration_token = JWT::encode( [ 'jti' => $token_id ], $client_secret );
$migration_token = JWT::encode(
[
'jti' => $token_id,
'scope' => 'migration_ws',
],
$client_secret
);
self::$opts->set( 'migration_ws', 1 );
self::$opts->set( 'client_secret', $client_secret );
self::$opts->set( 'migration_token', $migration_token );
Expand Down
22 changes: 22 additions & 0 deletions tests/testRoutesLogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,28 @@ public function testThatLoginRouteIsUnauthorizedIfWrongJti() {
$this->assertEquals( $output->error, $log[0]['message'] );
}

/**
* If the token has the wrong JTI, the route should fail with an error.
*/
public function testThatLoginRouteIsUnauthorizedIfMissingJti() {
$client_secret = '__test_client_secret__';
self::$opts->set( 'migration_ws', 1 );
self::$opts->set( 'client_secret', $client_secret );
self::$opts->set( 'migration_token_id', '__test_token_id__' );

self::$wp->query_vars['a0_action'] = 'migration-ws-login';
$_POST['access_token'] = JWT::encode( [ 'iss' => uniqid() ], $client_secret );

$output = json_decode( self::$routes->custom_requests( self::$wp ) );

$this->assertEquals( 401, $output->status );
$this->assertEquals( 'Invalid token', $output->error );

$log = self::$error_log->get();
$this->assertCount( 1, $log );
$this->assertEquals( $output->error, $log[0]['message'] );
}

/**
* If there is no username POSTed, the route should fail with an error.
*/
Expand Down

0 comments on commit 72385a1

Please sign in to comment.