Merge branch 'dev' into fix-silent-iconv-failure

isekai
Jonathan Daggerhart 5 years ago committed by GitHub
commit d91d6d2ea3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -298,6 +298,9 @@ class OpenID_Connect_Generic_Client_Wrapper {
// get the decoded response from the authentication request result
$token_response = $client->get_token_response( $token_result );
// allow for other plugins to alter data before validation
$token_response = apply_filters( 'openid-connect-modify-token-response-before-validation', $token_response );
if ( is_wp_error( $token_response ) ){
$this->error_redirect( $token_response );
}
@ -318,7 +321,10 @@ class OpenID_Connect_Generic_Client_Wrapper {
// The access_token must be used to prove access rights to protected resources
// e.g. for the userinfo endpoint
$id_token_claim = $client->get_id_token_claim( $token_response );
// allow for other plugins to alter data before validation
$id_token_claim = apply_filters( 'openid-connect-modify-id-token-claim-before-validation', $id_token_claim );
if ( is_wp_error( $id_token_claim ) ){
$this->error_redirect( $id_token_claim );
}
@ -356,12 +362,15 @@ class OpenID_Connect_Generic_Client_Wrapper {
$subject_identity = $client->get_subject_identity( $id_token_claim );
$user = $this->get_user_by_identity( $subject_identity );
// 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 ) ) {
$this->error_redirect( $user );
return;
if ( $this->settings->create_if_does_not_exist ) {
$user = $this->create_new_user( $subject_identity, $user_claim );
if ( is_wp_error( $user ) ) {
$this->error_redirect( $user );
}
}
else {
$this->error_redirect( new WP_Error( 'identity-not-map-existing-user', __( "User identity is not link to an existing WordPress user"), $user_claim ) );
}
}
else {
@ -527,7 +536,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
if ( empty( $transliterated_username ) ) {
return new WP_Error( 'username-transliteration-failed', __( "Username $desired_username could not be transliterated" ), $desired_username );
}
$normalized_username = strtolower( preg_replace( '/[^a-zA-Z\_0-9]/', '', $transliterated_username ) );
$normalized_username = strtolower( preg_replace( '/[^a-zA-Z0-9 _.\-@]/', '', $transliterated_username ) );
if ( empty( $normalized_username ) ) {
return new WP_Error( 'username-normalization-failed', __( "Username $transliterated_username could not be normalized" ), $transliterated_username );
}
@ -535,13 +544,15 @@ class OpenID_Connect_Generic_Client_Wrapper {
// copy the username for incrementing
$username = $normalized_username;
// original user gets "name"
// second user gets "name2"
// etc
$count = 1;
while ( username_exists( $username ) ) {
if (!$this->settings->link_existing_users) {
// original user gets "name"
// second user gets "name2"
// etc
$count = 1;
while ( username_exists( $username ) ) {
$count ++;
$username = $normalized_username . $count;
}
}
return $username;
@ -634,6 +645,8 @@ class OpenID_Connect_Generic_Client_Wrapper {
* @return \WP_Error | \WP_User
*/
function create_new_user( $subject_identity, $user_claim ) {
$user_claim = apply_filters( 'openid-connect-generic-alter-user-claim', $user_claim );
// default username & email to the subject identity
$username = $subject_identity;
$email = $subject_identity;
@ -733,7 +746,6 @@ class OpenID_Connect_Generic_Client_Wrapper {
return new WP_Error( 'cannot-authorize', __( 'Can not authorize.' ), $create_user );
}
$user_claim = apply_filters( 'openid-connect-generic-alter-user-claim', $user_claim );
$user_data = array(
'user_login' => $username,
'user_pass' => wp_generate_password( 32, true, true ),

@ -15,6 +15,9 @@ class OpenID_Connect_Generic_Client {
// states are only valid for 3 minutes
private $state_time_limit = 180;
// logger object
private $logger;
/**
* Client constructor
*
@ -27,7 +30,7 @@ class OpenID_Connect_Generic_Client {
* @param $redirect_uri
* @param $state_time_limit time states are valid in seconds
*/
function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $state_time_limit){
function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $state_time_limit, $logger){
$this->client_id = $client_id;
$this->client_secret = $client_secret;
$this->scope = $scope;
@ -36,6 +39,7 @@ class OpenID_Connect_Generic_Client {
$this->endpoint_token = $endpoint_token;
$this->redirect_uri = $redirect_uri;
$this->state_time_limit = $state_time_limit;
$this->logger = $logger;
}
/**
@ -51,12 +55,13 @@ class OpenID_Connect_Generic_Client {
$url = sprintf( '%1$s%2$sresponse_type=code&scope=%3$s&client_id=%4$s&state=%5$s&redirect_uri=%6$s',
$this->endpoint_login,
$separator,
urlencode( $this->scope ),
urlencode( $this->client_id ),
rawurlencode( $this->scope ),
rawurlencode( $this->client_id ),
$this->new_state(),
urlencode( $this->redirect_uri )
rawurlencode( $this->redirect_uri )
);
$this->logger->log( apply_filters( 'openid-connect-generic-auth-url', $url ), 'make_authentication_url' );
return apply_filters( 'openid-connect-generic-auth-url', $url );
}
@ -78,14 +83,19 @@ class OpenID_Connect_Generic_Client {
return new WP_Error( 'no-code', 'No authentication code present in the request.', $request );
}
// check the client request state
if ( ! isset( $request['state'] ) || ! $this->check_state( $request['state'] ) ){
// check the client request state
if( ! isset( $request['state']) ) {
do_action( 'openid-connect-generic-no-state-provided' );
return new WP_Error( 'missing-state', __( 'Missing state.' ), $request );
}
if ( ! $this->check_state( $request['state'] ) ) {
return new WP_Error( 'invalid-state', __( 'Invalid state.' ), $request );
}
return $request;
}
/**
* Get the authorization code from the request
*
@ -126,6 +136,7 @@ class OpenID_Connect_Generic_Client {
$request = apply_filters( 'openid-connect-generic-alter-request', $request, 'get-authentication-token' );
// call the server and ask for a token
$this->logger->log( $this->endpoint_token, 'request_authentication_token' );
$response = wp_remote_post( $this->endpoint_token, $request );
if ( is_wp_error( $response ) ){
@ -156,6 +167,7 @@ class OpenID_Connect_Generic_Client {
$request = apply_filters( 'openid-connect-generic-alter-request', $request, 'refresh-token' );
// call the server and ask for new tokens
$this->logger->log( $this->endpoint_token, 'request_new_tokens' );
$response = wp_remote_post( $this->endpoint_token, $request );
if ( is_wp_error( $response ) ) {
@ -222,6 +234,7 @@ class OpenID_Connect_Generic_Client {
$request['headers']['Host'] = $host;
// attempt the request including the access token in the query string for backwards compatibility
$this->logger->log( $this->endpoint_userinfo, 'request_userinfo' );
$response = wp_remote_post( $this->endpoint_userinfo, $request );
if ( is_wp_error( $response ) ){
@ -232,58 +245,47 @@ class OpenID_Connect_Generic_Client {
}
/**
* Generate a new state, save it to the states option with a timestamp,
* and return it.
* Generate a new state, save it as a transient,
* and return the state hash.
*
* @return string
*/
function new_state() {
$states = get_option( 'openid-connect-generic-valid-states', array() );
// new state w/ timestamp
$new_state = md5( mt_rand() . microtime( true ) );
$states[ $new_state ] = time();
$state = md5( mt_rand() . microtime( true ) );
set_transient( 'openid-connect-generic-state--' . $state, $state, $this->state_time_limit );
// save state
update_option( 'openid-connect-generic-valid-states', $states );
return $new_state;
return $state;
}
/**
* Check the validity of a given state
* Check the existence of a given state transient.
*
* @param $state
*
*
* @return bool
*/
function check_state( $state ) {
$states = get_option( 'openid-connect-generic-valid-states', array() );
$valid = false;
// remove any expired states
foreach ( $states as $code => $timestamp ) {
if ( ( $timestamp + $this->state_time_limit ) < time() ) {
unset( $states[ $code ] );
}
}
$state_found = true;
// see if the current state is still within the list of valid states
if ( isset( $states[ $state ] ) ) {
// state is valid, remove it
unset( $states[ $state ] );
$valid = true;
if ( ! get_option( '_transient_openid-connect-generic-state--' . $state ) ) {
do_action( 'openid-connect-generic-state-not-found', $state );
$state_found = false;
}
// save our altered states
update_option( 'openid-connect-generic-valid-states', $states );
$valid = get_transient( 'openid-connect-generic-state--' . $state );
return $valid;
if ( ! $valid && $state_found ) {
do_action( 'openid-connect-generic-state-expired', $state );
}
return !!$valid;
}
/**
* Ensure that the token meets basic requirements
*
*
* @param $token_response
*
* @return bool|\WP_Error

@ -133,7 +133,7 @@ class OpenID_Connect_Generic_Login_Form {
*/
function make_login_button() {
$text = apply_filters( 'openid-connect-generic-login-button-text', __( 'Login with OpenID Connect' ) );
$href = $this->client_wrapper->get_authentication_url();
$href = apply_filters( 'openid-connect-generic-login-button-url', $this->client_wrapper->get_authentication_url() );
ob_start();
?>

@ -142,7 +142,7 @@ class OpenID_Connect_Generic_Option_Logger {
'type' => $type,
'time' => time(),
'user_ID' => get_current_user_id(),
'uri' => $_SERVER['REQUEST_URI'],
'uri' => preg_replace('/code=([^&]+)/i', 'code=', $_SERVER['REQUEST_URI']),
'data' => $data,
);

@ -167,6 +167,12 @@ class OpenID_Connect_Generic_Settings_Page {
'type' => 'checkbox',
'section' => 'user_settings',
),
'create_if_does_not_exist' => array(
'title' => __( 'Create user if does not exist' ),
'description' => __( 'If the user identity is not link to an existing Wordpress user, it is created. If this setting is not enabled and if the user authenticates with an account which is not link to an existing Wordpress user then the authentication failed' ),
'type' => 'checkbox',
'section' => 'user_settings',
),
'redirect_user_back' => array(
'title' => __( 'Redirect Back to Origin Page' ),
'description' => __( 'After a successful OpenID Connect authentication, this will redirect the user back to the page on which they clicked the OpenID Connect login button. This will cause the login process to proceed in a traditional WordPress fashion. For example, users logging in through the default wp-login.php page would end up on the WordPress Dashboard and users logging in through the WooCommerce "My Account" page would end up on their account page.' ),
@ -361,6 +367,10 @@ class OpenID_Connect_Generic_Settings_Page {
<strong><?php _e( 'Login Button Shortcode' ); ?></strong>
<code>[openid_connect_generic_login_button]</code>
</p>
<p class="description">
<strong><?php _e( 'Authentication URL Shortcode' ); ?></strong>
<code>[openid_connect_generic_auth_url]</code>
</p>
<?php if ( $this->settings->enable_logging ) { ?>
<h2><?php _e( 'Logs' ); ?></h2>

@ -3,7 +3,7 @@
Plugin Name: OpenID Connect Generic
Plugin URI: https://github.com/daggerhart/openid-connect-generic
Description: Connect to an OpenID Connect generic client using Authorization Code Flow
Version: 3.5.0
Version: 3.6.0
Author: daggerhart
Author URI: http://www.daggerhart.com
License: GPLv2 Copyright (c) 2015 daggerhart
@ -23,6 +23,8 @@ Notes
- openid-connect-generic-auth-url - modify the authentication url
- openid-connect-generic-alter-user-claim - modify the user_claim before a new user is created
- openid-connect-generic-alter-user-data - modify user data before a new user is created
- openid-connect-modify-token-response-before-validation - modify the token response before validation
- openid-connect-modify-id-token-claim-before-validation - modify the token claim before validation
Actions
- openid-connect-generic-user-create - 2 args: fires when a new user is created by this plugin
@ -30,6 +32,9 @@ Notes
- openid-connect-generic-update-user-using-current-claim - 2 args: fires every time an existing user logs
- openid-connect-generic-redirect-user-back - 2 args: $redirect_url, $user. Allows interruption of redirect during login.
- openid-connect-generic-user-logged-in - 1 arg: $user, fires when user is logged in.
- openid-connect-generic-cron-daily - daily cron action
- openid-connect-generic-state-not-found - the given state does not exist in the database, regardless of its expiration.
- openid-connect-generic-state-expired - the given state exists, but expired before this login attempt.
User Meta
- openid-connect-generic-subject-identity - the identity of the user provided by the idp
@ -45,7 +50,7 @@ Notes
class OpenID_Connect_Generic {
// plugin version
const VERSION = '3.5.0';
const VERSION = '3.6.0';
// plugin settings
private $settings;
@ -97,7 +102,8 @@ class OpenID_Connect_Generic {
$this->settings->endpoint_userinfo,
$this->settings->endpoint_token,
$redirect_uri,
$state_time_limit
$state_time_limit,
$this->logger
);
$this->client_wrapper = OpenID_Connect_Generic_Client_Wrapper::register( $this->client, $this->settings, $this->logger );
@ -110,6 +116,9 @@ class OpenID_Connect_Generic {
// add a shortcode to get the auth url
add_shortcode( 'openid_connect_generic_auth_url', array( $this->client_wrapper, 'get_authentication_url' ) );
// add actions to our scheduled cron jobs
add_action( 'openid-connect-generic-cron-daily', [ $this, 'cron_states_garbage_collection'] );
$this->upgrade();
if ( is_admin() ){
@ -153,6 +162,7 @@ class OpenID_Connect_Generic {
if ( version_compare( self::VERSION, $last_version, '>' ) ) {
// upgrade required
self::setup_cron_jobs();
// @todo move this to another file for upgrade scripts
if ( isset( $settings->ep_login ) ) {
@ -169,6 +179,45 @@ class OpenID_Connect_Generic {
}
}
/**
* Expire state transients by attempting to access them and allowing the
* transient's own mechanisms to delete any that have expired.
*/
function cron_states_garbage_collection() {
global $wpdb;
$states = $wpdb->get_col( "SELECT `option_name` FROM {$wpdb->options} WHERE `option_name` LIKE '_transient_openid-connect-generic-state--%'" );
if ( !empty( $states ) ) {
foreach ( $states as $state ) {
$transient = str_replace("_transient_", "", $state);
get_transient( $transient );
}
}
}
/**
* Ensure cron jobs are added to the schedule.
*/
static public function setup_cron_jobs() {
if ( ! wp_next_scheduled( 'openid-connect-generic-cron-daily' ) ) {
wp_schedule_event( time(), 'daily', 'openid-connect-generic-cron-daily' );
}
}
/**
* Activation hook.
*/
static public function activation() {
self::setup_cron_jobs();
}
/**
* Deactivation hook.
*/
static public function deactivation() {
wp_clear_scheduled_hook( 'openid-connect-generic-cron-daily' );
}
/**
* Simple autoloader
*
@ -231,6 +280,7 @@ class OpenID_Connect_Generic {
'enforce_privacy' => 0,
'alternate_redirect_uri' => 0,
'link_existing_users' => 0,
'create_if_does_not_exist' => 1,
'redirect_user_back' => 0,
'redirect_on_logout' => 1,
'enable_logging' => 0,
@ -253,3 +303,6 @@ class OpenID_Connect_Generic {
}
OpenID_Connect_Generic::bootstrap();
register_activation_hook( __FILE__, [ 'OpenID_Connect_Generic', 'activation' ] );
register_deactivation_hook( __FILE__, [ 'OpenID_Connect_Generic', 'deactivation' ] );

@ -50,6 +50,18 @@ On the settings page for this plugin (Dashboard > Settings > OpenID Connect Gene
== Changelog ==
= 3.6.0 =
* Improvement: @RobjS - Improved error messages during login state failure.
* Improvement: @RobjS - New developer filter for login form button URL.
* Fix: @cs1m0n - Only increment username during new user creation if the "Link existing user" setting is enabled.
* Fix: @xRy-42 - Allow periods and spaces in usernames to match what WordPress core allows.
* Feature: @benochen - New setting named "Create user if does not exist" determines whether new users are created during login attempts.
= 3.5.1 =
* Fix: @daggerhart - New approach to state management using transients.
= 3.5.0 =
* Readme fix: @thijskh - Fix syntax error in example openid-connect-generic-login-button-text

Loading…
Cancel
Save