major refactor to separate responsibility

isekai
Jonathan Daggerhart 10 years ago
parent 0ddb501a55
commit 2e3167fa20

@ -0,0 +1,455 @@
<?php
class OpenID_Connect_Generic_Client_Wrapper {
private $client;
// settings object
private $settings;
// logger object
private $logger;
// internal tracking cookie key
private $cookie_id_key = 'openid-connect-generic-identity';
// WP_Error if there was a problem, or false if no error
private $error = false;
/**
* Inject necessary objects and services into the client
*
* @param \WP_Option_Settings $settings
* @param \WP_Option_Logger $logger
*/
function __construct( OpenID_Connect_Generic_Client $client, WP_Option_Settings $settings, WP_Option_Logger $logger ){
$this->client = $client;
$this->settings = $settings;
$this->logger = $logger;
}
/**
* Hook the client into WP
*
* @param \OpenID_Connect_Generic_Client $client
* @param \WP_Option_Settings $settings
* @param \WP_Option_Logger $logger
*/
static public function register( OpenID_Connect_Generic_Client $client, WP_Option_Settings $settings, WP_Option_Logger $logger ){
$client_wrapper = new self( $client, $settings, $logger );
// remove cookies on logout
add_action( 'wp_logout', array( $client_wrapper, 'wp_logout' ) );
// verify legitimacy of user token on admin pages
add_action( 'admin_init', array( $client_wrapper, 'check_user_token' ) );
// alter the requests according to settings
add_filter( 'openid-connect-generic-alter-request', array( $client_wrapper, 'alter_request' ), 10, 3 );
if ( is_admin() ) {
// use the ajax url to handle processing authorization without any html output
// this callback will occur when then IDP returns with an authenticated value
add_action( 'wp_ajax_openid-connect-authorize', array( $client_wrapper, 'authentication_request_callback' ) );
add_action( 'wp_ajax_nopriv_openid-connect-authorize', array( $client_wrapper, 'authentication_request_callback' ) );
}
$client_wrapper->startup();
return $client_wrapper;
}
/**
* Handle the initial validation that should occur on each page load
*/
function startup(){
$this->handle_privacy();
// verify token for any logged in user
if ( is_user_logged_in() ) {
$this->check_user_token();
}
}
/**
* Get the authentication url from the client
*
* @return string
*/
function get_authentication_url(){
return $this->client->make_authentication_url();
}
/**
* Handle the privacy settings
*/
function handle_privacy() {
// check if privacy enforcement is enabled
if ( $this->settings->enforce_privacy &&
! is_user_logged_in() &&
// avoid redirects on cron or ajax
( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) &&
( ! defined( 'DOING_CRON' ) || ! DOING_CRON )
) {
global $pagenow;
// avoid redirect loop
if ( $pagenow != 'wp-login.php' && ! isset( $_GET['loggedout'] ) && ! isset( $_GET['login-error'] ) ) {
$this->error_redirect( new WP_Error( 'privacy', __( 'This site requires login.' ), $_GET ) );
}
}
}
/**
* Check the user's cookie
*/
function check_user_token() {
$is_openid_connect_user = get_user_meta( wp_get_current_user()->ID, 'openid-connect-generic-user', TRUE );
if ( is_user_logged_in() && ! empty( $is_openid_connect_user ) && ! isset( $_COOKIE[ $this->cookie_id_key ] ) ) {
wp_logout();
$this->error_redirect( new WP_Error( 'mismatch-identity', __( 'Mismatch identity' ), $_COOKIE ) );
}
}
/**
* Handle errors by redirecting the user to the login form
* along with an error code
*
* @param $error WP_Error
*/
function error_redirect( $error ) {
$this->logger->log( $error );
// redirect user back to login page
wp_redirect(
wp_login_url() .
'?login-error=' . $error->get_error_code() .
'&message=' . urlencode( $error->get_error_message() )
);
exit;
}
/**
* Get the current error state
*
* @return bool | WP_Error
*/
function get_error(){
return $this->error;
}
/**
* Implements hook wp_logout
*
* Remove cookies
*/
function wp_logout() {
setcookie( $this->cookie_id_key, '1', 0, COOKIEPATH, COOKIE_DOMAIN, TRUE );
}
/**
* Modify outgoing requests according to settings
*
* @param $request
* @param $op
*
* @return mixed
*/
function alter_request( $request, $op ) {
if ( $this->settings->no_sslverify ) {
$request['sslverify'] = FALSE;
}
return $request;
}
/**
* Control the authentication and subsequent authorization of the user when
* returning from the IDP.
*/
function authentication_request_callback() {
$settings = $this->settings;
$client = $this->client;
//
$authentication_request = $client->validate_authentication_request( $_GET );
if ( is_wp_error( $authentication_request ) ){
$this->error_redirect( $authentication_request );
}
// retrieve the authentication code from the authentication request
$code = $client->get_authentication_code( $authentication_request );
if ( is_wp_error( $code ) ){
$this->error_redirect( $code );
}
// attempting to exchange an authorization code for an authentication token
$token_result = $client->request_authentication_token( $code );
if ( is_wp_error( $token_result ) ) {
$this->error_redirect( $token_result );
}
// get the decoded response from the authentication request result
$token_response = $client->get_token_response( $token_result );
if ( is_wp_error( $token_response ) ){
$this->error_redirect( $token_response );
}
// ensure the that response contains required information
$valid = $client->validate_token_response( $token_response );
if ( is_wp_error( $valid ) ) {
$this->error_redirect( $valid );
}
// - end authentication
// - start authorization
// The id_token is used to identify the authenticated user, e.g. for SSO.
// 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 );
if ( is_wp_error( $id_token_claim ) ){
$this->error_redirect( $id_token_claim );
}
//
$valid = $client->validate_id_token_claim( $id_token_claim );
if ( is_wp_error( $valid ) ){
$this->error_redirect( $valid );
}
//
// // if desired, admins can use regex to determine if the identity value is valid
// // according to their own standards expectations
// if ( ! empty( $settings->allowed_regex ) &&
// preg_match( $settings->allowed_regex, $id_token_claim[ $settings->identity_key ] ) !== 1
// ) {
// return new WP_Error( 'no-subject-identity', __( 'No subject identity' ), $id_token_claim );
// }
//
$user_claim = $client->get_user_claim( $token_response );
if ( is_wp_error( $user_claim ) ){
$this->error_redirect( $user_claim );
}
//
$valid = $client->validate_user_claim( $user_claim, $id_token_claim );
if ( is_wp_error( $valid ) ){
$this->error_redirect( $valid );
}
// - end authorization
// request is authenticated and authorized
// - start user handling
$user_identity = $client->get_user_identity( $id_token_claim );
$user = $this->get_user_by_identity( $user_identity );
// if we didn't find an existing user, we'll need to create it
if ( ! $user ) {
$user = $this->create_new_user( $user_identity, $user_claim );
}
//
$valid = $this->validate_user( $user );
if ( is_wp_error( $valid ) ){
$this->error_redirect( $valid );
}
$this->login_user( $user, $token_response, $id_token_claim, $user_claim, $user_identity );
$this->logger->log( "Successful login for: {$user->user_login} ({$user->ID})", 'login-success' );
wp_redirect( home_url() );
}
/**
* Validate the potential WP_User
*
* @param $user
*
* @return \WP_Error
*/
function validate_user( $user ){
// ensure our found user is a real WP_User
if ( ! is_a( $user, 'WP_User' ) || ! $user->exists() ) {
return new WP_Error( 'invalid-user', __( 'Invalid user' ), $user );
}
return true;
}
/**
*
*
* @param $user
*/
function login_user( $user, $token_response, $id_token_claim, $user_claim, $user_identity ){
// hey, we made it!
// let's remember the tokens for future reference
update_user_meta( $user->ID, 'openid-connect-generic-last-token-response', $token_response );
update_user_meta( $user->ID, 'openid-connect-generic-last-id-token-claim', $id_token_claim );
update_user_meta( $user->ID, 'openid-connect-generic-last-user-claim', $user_claim );
// save our authorization cookie for the response expiration
$oauth_expiry = $token_response['expires_in'] + current_time( 'timestamp', TRUE );
setcookie( $this->cookie_id_key, $user_identity, $oauth_expiry, COOKIEPATH, COOKIE_DOMAIN, TRUE );
// get a cookie and go home!
wp_set_auth_cookie( $user->ID, FALSE );
}
/**
*
*
* @param $user_identity
*
* @return false|\WP_User
*/
function get_user_by_identity( $user_identity ){
// look for user by their openid-connect-generic-user-identity value
$user_query = new WP_User_Query( array(
'meta_query' => array(
array(
'key' => 'openid-connect-generic-user-identity',
'value' => $user_identity,
)
)
) );
// if we found an existing users, grab the first one returned
if ( $user_query->get_total() > 0 ) {
$users = $user_query->get_results();
return $users[0];
}
return false;
}
/**
* Avoid user_login collisions by incrementing
*
* @param $user_claim array
*
* @return string
*/
private function get_username_from_claim( $user_claim ) {
if ( isset( $user_claim['preferred_username'] ) && ! empty( $user_claim['preferred_username'] ) ) {
$desired_username = $user_claim['preferred_username'];
}
else if ( isset( $user_claim['name'] ) && ! empty( $user_claim['name'] ) ) {
$desired_username = $user_claim['name'];
}
else if ( isset( $user_claim['email'] ) && ! empty( $user_claim['email'] ) ) {
$tmp = explode( '@', $user_claim['email'] );
$desired_username = $tmp[0];
}
else {
// nothing to build a name from
return new WP_Error( 'no-username', __( 'No appropriate username found' ), $user_claim );
}
// normalize the data a bit
$desired_username = strtolower( preg_replace( '/[^a-zA-Z\_0-9]/', '', $desired_username ) );
// copy the username for incrementing
$username = $desired_username;
// original user gets "name"
// second user gets "name2"
// etc
$count = 1;
while ( username_exists( $username ) ) {
$count ++;
$username = $desired_name . $count;
}
return $username;
}
/**
*
*
* @param $user_identity
* @param $user_claim
*
* @return \WP_Error | \WP_User
*/
function create_new_user( $user_identity, $user_claim){
// default username & email to the user identity, since that is the only
// thing we can be sure to have
$username = $user_identity;
$email = $user_identity;
// allow claim details to determine username
if ( isset( $user_claim['email'] ) ) {
$email = $user_claim['email'];
$username = $this->get_username_from_claim( $user_claim );
}
// if no name exists, attempt another request for userinfo
else if ( isset( $token_response['access_token'] ) ) {
$user_claim_result = $this->client->request_userinfo( $token_response['access_token'] );
// make sure we didn't get an error
if ( is_wp_error( $user_claim_result ) ) {
return new WP_Error( 'bad-user-claim-result', __( 'Bad user claim result' ), $user_claim_result );
}
$user_claim = json_decode( $user_claim_result['body'], TRUE );
if ( isset( $user_claim['email'] ) ) {
$email = $user_claim['email'];
$username = $this->get_username_from_claim( $user_claim );
}
}
// allow other plugins / themes to determine authorization
// of new accounts based on the returned user claim
$create_user = apply_filters( 'openid-connect-generic-user-creation-test', TRUE, $user_claim );
if ( ! $create_user ) {
return new WP_Error( 'cannot-authorize', __( 'Can not authorize.' ), $create_user );
}
// create the new user
$uid = wp_create_user( $username, wp_generate_password( 32, TRUE, TRUE ), $email );
// make sure we didn't fail in creating the user
if ( is_wp_error( $uid ) ) {
return new WP_Error( 'failed-user-creation', __( 'Failed user creation.' ), $uid );
}
$user = get_user_by( 'id', $uid );
$this->log( "New user created: {$user->user_login} ($uid)", 'success' );
// save some meta data about this new user for the future
add_user_meta( $user->ID, 'openid-connect-generic-user', TRUE, TRUE );
add_user_meta( $user->ID, 'openid-connect-generic-user-identity', (string) $user_identity, TRUE );
// allow plugins / themes to take action on new user creation
do_action( 'openid-connect-generic-user-create', $user, $user_claim );
return $user;
}
}

@ -0,0 +1,323 @@
<?php
class OpenID_Connect_Generic_Client {
private $client_id;
private $client_secret;
private $scope;
private $endpoint_login;
private $endpoint_userinfo;
private $endpoint_token;
// login flow "ajax" endpoint
private $redirect_uri;
// states are only valid for 3 minutes
private $state_time_limit = 180;
/**
* @param $client_id
* @param $client_secret
* @param $scope
* @param $endpoint_login
* @param $endpoint_userinfo
* @param $endpoint_token
* @param $redirect_uri
*/
function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri ){
$this->client_id = $client_id;
$this->client_secret = $client_secret;
$this->scope = $scope;
$this->endpoint_login = $endpoint_login;
$this->endpoint_userinfo = $endpoint_userinfo;
$this->endpoint_token = $endpoint_token;
$this->redirect_uri = $redirect_uri;
}
/**
* Create a single use authentication url
*
* @return string
*/
function make_authentication_url() {
$url = sprintf( '%1$s?response_type=code&scope=%2$s&client_id=%3$s&state=%4$s&redirect_uri=%5$s',
$this->endpoint_login,
urlencode( $this->scope ),
urlencode( $this->client_id ),
$this->new_state(),
urlencode( $this->redirect_uri )
);
return $url;
}
/**
*
*
* @param $request
*
* @return \WP_Error
*/
function validate_authentication_request( $request ){
// look for an existing error of some kind
if ( isset( $request['error'] ) ) {
return new WP_Error( 'unknown-error', 'An unknown error occurred.', $request );
}
// make sure we have a legitimate authentication code and valid state
if ( ! isset( $request['code'] ) ) {
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'] ) ){
return new WP_Error( 'missing-state', __( 'Missing state.' ), $request );
}
return $request;
}
/**
* Get the authorization code from the request
*
* @return string
*/
function get_authentication_code( $request ){
return $request['code'];
}
/**
* Using the authorization_code, request an authentication token from the idp
*
* @param $code - authorization_code
*
* @return array|\WP_Error
*/
function request_authentication_token( $code ) {
$request = array(
'body' => array(
'code' => $code,
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'redirect_uri' => $this->redirect_uri,
'grant_type' => 'authorization_code',
'scope' => $this->scope,
)
);
// allow modifications to the request
$request = apply_filters( 'openid-connect-generic-alter-request', $request, 'get-authentication-token' );
// call the server and ask for a token
$response = wp_remote_post( $this->endpoint_token, $request );
return $response;
}
/**
*
*
* @param $token_result
* @return array|mixed|object
*/
function get_token_response( $token_result ){
if ( ! isset( $token_result['body'] ) ){
return new WP_Error( 'missing-token-body', __( 'Missing token body.' ), $token_response );
}
// extract token response from token
$token_response = json_decode( $token_result['body'], TRUE );
return $token_response;
}
/**
* Using an access_token, request the userinfo from the idp
*
* @param $access_token
*
* @return array|\WP_Error
*/
function request_userinfo( $access_token ) {
// allow modifications to the request
$request = apply_filters( 'openid-connect-generic-alter-request', array(), 'get-userinfo' );
// attempt the request
$response = wp_remote_get( $this->endpoint_userinfo . '?access_token=' . $access_token, $request );
return $response;
}
/**
* Generate a new state, save it to the states option with a timestamp,
* and return it.
*
* @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();
// save state
update_option( 'openid-connect-generic-valid-states', $states );
return $new_state;
}
/**
* Check the validity of a given state
*
* @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 ] );
}
}
// 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;
}
// save our altered states
update_option( 'openid-connect-generic-valid-states', $states );
return $valid;
}
/**
*
*
* @param $token_response
*
* @return bool|\WP_Error
*/
function validate_token_response( $token_response ){
// we need to ensure 3 specific items exist with the token response in order
// to proceed with confidence: id_token, access_token, and token_type == 'Bearer'
if ( ! isset( $token_response['id_token'] ) || ! isset( $token_response['access_token'] ) ||
! isset( $token_response['token_type'] ) || $token_response['token_type'] !== 'Bearer'
) {
return new WP_Error( 'invalid-token-response', 'Invalid token response', $token_response );
}
return true;
}
/**
*
*
* @param $token_response
*
* @return array|mixed|object|\WP_Error
*/
function get_id_token_claim( $token_response ){
// name sure we have an id_token
if ( ! isset( $token_response['id_token'] ) ) {
return new WP_Error( 'no-identity-token', __( 'No identity token' ), $token_response );
}
// break apart the id_token in the response for decoding
$tmp = explode( '.', $token_response['id_token'] );
if ( ! isset( $tmp[1] ) ) {
return new WP_Error( 'no-identity-token', __( 'No identity token' ), $token_response );
}
// Extract the id_token's claims from the token
$id_token_claim = json_decode( base64_decode( $tmp[1] ), TRUE );
return $id_token_claim;
}
/**
*
*
* @param $id_token_claim
*
* @return bool|\WP_Error
*/
function validate_id_token_claim( $id_token_claim ){
// make sure we can find our identification data and that it has a value
if ( ! isset( $id_token_claim['sub'] ) || empty( $id_token_claim['sub'] ) ) {
return new WP_Error( 'no-subject-identity', __( 'No subject identity' ), $id_token_claim );
}
return true;
}
/**
*
*
* @param $token_response
*
* @return array|mixed|object|\WP_Error
*/
function get_user_claim( $token_response ){
// send a userinfo request to get user claim
$user_claim_result = $this->request_userinfo( $token_response['access_token'] );
// make sure we didn't get an error, and that the response body exists
if ( is_wp_error( $user_claim_result ) || ! isset( $user_claim_result['body'] ) ) {
return new WP_Error( 'bad-claim', __( 'Bad user claim' ), $user_claim_result );
}
$user_claim = json_decode( $user_claim_result['body'], TRUE );
return $user_claim;
}
/**
*
*
* @param $user_claim
* @param $id_token_claim
*
* @return \WP_Error
*/
function validate_user_claim( $user_claim, $id_token_claim ) {
// must be an array
if ( ! is_array( $user_claim ) ){
return new WP_Error( 'invalid-user-claim', __( 'Invalid user claim' ), $user_claim );
}
// make sure the id_token sub === user_claim sub, according to spec
if ( $id_token_claim['sub' ] !== $user_claim['sub'] ) {
return new WP_Error( 'invalid-user-claim', __( 'Invalid user claim' ), func_get_args() );
}
// allow for other plugins to alter the login success
$login_user = apply_filters( 'openid-connect-generic-user-login-test', TRUE, $user_claim );
if ( ! $login_user ) {
return new WP_Error( 'unauthorized', __( 'Unauthorized' ), $login_user );
}
return true;
}
/**
*
*
* @return mixed
*/
function get_user_identity( $id_token_claim ){
return $id_token_claim['sub'];
}
}

@ -0,0 +1,94 @@
<?php
class OpenID_Connect_Generic_Login_Form {
private $settings;
private $client_wrapper;
/**
* @param $settings
* @param $client_wrapper
*/
function __construct( $settings, $client_wrapper ){
$this->settings = $settings;
$this->client_wrapper = $client_wrapper;
}
/**
* @param $settings
* @param $client_wrapper
*
* @return \OpenID_Connect_Generic_Login_Form
*/
static public function register( $settings, $client_wrapper ){
$login_form = new self( $settings, $client_wrapper );
// alter the login form as dictated by settings
add_filter( 'login_message', array( $login_form, 'handle_login_page' ), 99 );
return $login_form;
}
/**
* Implements filter login_message
*
* @param $message
* @return string
*/
function handle_login_page( $message ) {
$settings = $this->settings;
// errors and auto login can't happen at the same time
if ( isset( $_GET['login-error'] ) ) {
$message = $this->make_error_output( $_GET['login-error'], $_GET['message'] );
}
else if ( $settings->login_type == 'auto' ) {
wp_redirect( $this->client_wrapper->get_authentication_url() );
exit;
}
// login button is appended to existing messages in case of error
if ( $settings->login_type == 'button' ) {
$message .= $this->make_login_button();
}
return $message;
}
/**
* Display an error message to the user
*
* @param $error_code
*
* @return string
*/
function make_error_output( $error_code, $error_message ) {
ob_start();
?>
<div id="login_error">
<strong><?php _e( 'ERROR'); ?>: </strong>
<?php print $error_message; ?>
</div>
<?php
return ob_get_clean();
}
/**
* Create a login button (link)
*
* @return string
*/
function make_login_button() {
$text = apply_filters( 'openid-connect-generic-login-button-text', __( 'Login with OpenID Connect' ) );
$href = $this->client_wrapper->get_authentication_url();
ob_start();
?>
<div class="openid-connect-login-button" style="margin: 1em 0; text-align: center;">
<a class="button button-large" href="<?php print esc_url( $href ); ?>"><?php print $text; ?></a>
</div>
<?php
return ob_get_clean();
}
}

@ -1,9 +1,9 @@
<?php
class OpenID_Connect_Generic_Settings {
class OpenID_Connect_Generic_Settings_Page {
// local copy of the settings provided by the base plugin
private $settings = array();
private $settings;
// The controlled list of settings & associated
// defined during construction for i18n reasons
@ -13,20 +13,16 @@ class OpenID_Connect_Generic_Settings {
private $options_page_name = 'openid-connect-generic-settings';
// options page settings group name
private $settings_field_group = '';
private $settings_field_group;
/**
* @param $settings
* @param \WP_Option_Settings $settings
* @param \WP_Option_Logger $logger
*/
function __construct( $settings ) {
function __construct( WP_Option_Settings $settings, WP_Option_Logger $logger ) {
$this->settings = $settings;
$this->settings_field_group = OPENID_CONNECT_GENERIC_SETTINGS_NAME . '-group';
// add our options page the the admin menu
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
// register our settings
add_action( 'admin_init', array( $this, 'admin_init' ) );
$this->logger = $logger;
$this->settings_field_group = $this->settings->get_option_name() . '-group';
/*
* Simple settings fields simply have:
@ -133,13 +129,31 @@ class OpenID_Connect_Generic_Settings {
// some simple pre-processing
foreach ( $fields as $key => &$field ) {
$field['key'] = $key;
$field['name'] = OPENID_CONNECT_GENERIC_SETTINGS_NAME . '[' . $key . ']';
$field['name'] = $this->settings->get_option_name() . '[' . $key . ']';
}
// allow alterations of the fields
$this->settings_fields = $fields;
}
/**
* @param \WP_Option_Settings $settings
* @param \WP_Option_Logger $logger
*
* @return \OpenID_Connect_Generic_Settings_Page
*/
static public function register( WP_Option_Settings $settings, WP_Option_Logger $logger ){
$settings_page = new self( $settings, $logger );
// add our options page the the admin menu
add_action( 'admin_menu', array( $settings_page, 'admin_menu' ) );
// register our settings
add_action( 'admin_init', array( $settings_page, 'admin_init' ) );
return $settings_page;
}
/**
* Implements hook admin_menu to add our options/settings page to the
* dashboard menu
@ -157,7 +171,7 @@ class OpenID_Connect_Generic_Settings {
* Implements hook admin_init to register our settings
*/
public function admin_init() {
register_setting( $this->settings_field_group, OPENID_CONNECT_GENERIC_SETTINGS_NAME, array(
register_setting( $this->settings_field_group, $this->settings->get_option_name(), array(
$this,
'sanitize_settings'
) );
@ -183,8 +197,8 @@ class OpenID_Connect_Generic_Settings {
// preprocess fields and add them to the page
foreach ( $this->settings_fields as $key => $field ) {
// make sure each key exists in the settings array
if ( ! isset( $this->settings[ $key ] ) ) {
$this->settings[ $key ] = NULL;
if ( ! isset( $this->settings->{ $key } ) ) {
$this->settings->{ $key } = NULL;
}
// determine appropriate output callback
@ -260,31 +274,7 @@ class OpenID_Connect_Generic_Settings {
</p>
<?php
$logs = get_option( 'openid_connect_generic_logs', array() );
if ( ! empty( $logs ) ) {
?>
<h4><?php _e( 'Logs' ); ?></h4>
<table class="wp-list-table widefat fixed striped posts">
<thead>
<th>Type</th>
<th>Date</th>
<th>User</th>
<th style="width: 65%;">Data</th>
</thead>
<tbody>
<?php foreach ( $logs as $log ) { ?>
<tr>
<td><?php print $log['type']; ?></td>
<td><?php print date( 'Y-m-d H:i:s', $log['time'] ); ?></td>
<td><?php print ( $log['user_ID'] ) ? get_userdata( $log['user_ID'] )->user_login : 'anonymous'; ?></td>
<td><?php print '<pre style="margin:0;">' . print_r( $log['data'], 1 ) . '</pre>'; ?></td>
</tr>
<?php } ?>
</tbody>
</table>
<?php
}
//$this->show_logs();
?>
</div>
<?php
@ -301,7 +291,7 @@ class OpenID_Connect_Generic_Settings {
id="<?php print esc_attr( $field['key'] ); ?>"
class="large-text"
name="<?php print esc_attr( $field['name'] ); ?>"
value="<?php print esc_attr( $this->settings[ $field['key'] ] ); ?>">
value="<?php print esc_attr( $this->settings->{ $field['key'] } ); ?>">
<?php
$this->do_field_description( $field );
}
@ -319,7 +309,7 @@ class OpenID_Connect_Generic_Settings {
id="<?php print esc_attr( $field['key'] ); ?>"
name="<?php print esc_attr( $field['name'] ); ?>"
value="1"
<?php checked( $this->settings[ $field['key'] ], 1 ); ?>>
<?php checked( $this->settings->{ $field['key'] }, 1 ); ?>>
<?php
$this->do_field_description( $field );
}
@ -328,7 +318,7 @@ class OpenID_Connect_Generic_Settings {
* @param $field
*/
function do_select( $field ) {
$current_value = ( $this->settings[ $field['key'] ] ? $this->settings[ $field['key'] ] : '' );
$current_value = isset( $this->settings->{ $field['key'] } ) ? $this->settings->{ $field['key'] } : '';
?>
<select name="<?php print esc_attr( $field['name'] ); ?>">
<?php foreach ( $field['options'] as $value => $text ): ?>

@ -0,0 +1,223 @@
<?php
/**
* Simple class for logging messages to the options table
*/
if ( !class_exists( 'WP_Option_Logger' ) ) :
class WP_Option_Logger {
// wp option name/key
private $option_name;
// default message type
private $default_message_type;
// the number of items to keep in the log
private $log_limit;
// whether or not the
private $logging_enabled;
// internal cache of logs
private $logs;
/**
* Setup the logger according to the needs of the instance
*
* @param string $option_name
* @param string $default_message_type
* @param bool|TRUE $logging_enabled
* @param int $log_limit
*/
function __construct( $option_name, $default_message_type = 'none', $logging_enabled = true, $log_limit = 1000 ){
$this->option_name = $option_name;
$this->default_message_type = $default_message_type;
$this->logging_enabled = (bool) $logging_enabled;
$this->log_limit = (int) $log_limit;
}
/**
* Subscribe logger to a set of filters
*
* @param $filter_names
* @param int $priority
*/
function log_filters( $filter_names, $priority = 10 ){
if ( ! is_array( $filter_names ) ) {
$filter_names = array( $filter_names );
}
foreach ( $filter_names as $filter ){
add_filter( $filter, array( $this, 'log_hook' ), $priority );
}
}
/**
* Subscribe logger to a set of actions
*
* @param $action_names
* @param $priority
*/
function log_actions( $action_names, $priority ){
if ( ! is_array( $action_names ) ) {
$action_names = array( $action_names );
}
foreach ( $action_names as $action ){
add_filter( $action, array( $this, 'log_hook' ), $priority );
}
}
/**
* Log the data
*
* @param null $arg1
* @return null
*/
function log_hook( $arg1 = null ){
$this->log( func_get_args(), current_filter() );
return $arg1;
}
/**
* Save an array of data to the logs
*
* @param $data array
* @return bool
*/
public function log( $data, $type = null ) {
if ( (bool) $this->logging_enabled ) {
$logs = $this->get_logs();
$logs[] = $this->make_message( $data, $type );
$logs = $this->upkeep_logs( $logs );
return $this->save_logs( $logs );
}
return false;
}
/**
* Retrieve all log messages
*
* @return array
*/
public function get_logs() {
if ( is_null( $this->logs ) ) {
$this->logs = get_option( $this->option_name, array() );
}
return $this->logs;
}
/**
* Create a message array containing the data and other information
*
* @param $data (mixed)
* @return array
*/
private function make_message( $data, $type ){
// determine the type of message
if ( empty( $type ) ) {
$this->default_message_type;
if ( is_array( $data ) && isset( $data['type'] ) ){
$type = $data['type'];
}
else if ( is_wp_error( $data ) ){
$type = $data->get_error_code();
}
}
// construct our message
$message = array(
'type' => $type,
'time' => time(),
'user_ID' => get_current_user_id(),
'uri' => $_SERVER['REQUEST_URI'],
'data' => $data,
);
return $message;
}
/**
* Keep our log count under the limit
*
* @param $message array - extra data about the message
* @return array
*/
private function upkeep_logs( $logs ) {
$items_to_remove = count( $logs ) - $this->log_limit;
if ( $items_to_remove > 0 ){
// keep only the last $log_limit messages from the end
$logs = array_slice( $logs, ( $items_to_remove * -1) );
}
return $logs;
}
/**
* Save the log messages
*
* @param $logs
* @return bool
*/
private function save_logs( $logs ){
// save our logs
$this->logs = $logs;
return update_option( $this->option_name, $logs, FALSE );
}
/**
* Get a simple html table of all the logs
*
* @param array $logs
* @return string
*/
public function get_logs_table( $logs = array() ){
if ( empty( $logs ) ) {
$logs = $this->get_logs();
}
ob_start();
?>
<table class="wp-list-table widefat fixed striped posts">
<thead>
<th>Details</th>
<th style="width: 85%;">Data</th>
</thead>
<tbody>
<?php foreach ( $logs as $log ) { ?>
<tr>
<td>
<div>
<label><?php _e( 'Type' ); ?>: </label>
<?php print $log['type']; ?>
</div>
<div>
<label><?php _e( 'Date' ); ?>: </label>
<?php print date( 'Y-m-d H:i:s', $log['time'] ); ?>
</div>
<div>
<label><?php _e( 'User' ); ?>: </label>
<?php print ( $log['user_ID'] ) ? get_userdata( $log['user_ID'] )->user_login : '0'; ?>
</div>
<div>
<label><?php _e( 'URI: ' ); ?>: </label>
<?php print $log['uri']; ?>
</div>
</td>
<td><?php var_dump( $log['data'] ); ?></td>
</tr>
<?php } ?>
</tbody>
</table>
<?php
$output = ob_get_clean();
return $output;
}
}
endif;

@ -0,0 +1,45 @@
<?php
if ( ! class_exists( 'WP_Option_Settings' ) ) :
class WP_Option_Settings {
// wp option name/key
private $option_name;
// stored option values array
private $values = array();
// default plugin settings values
private $default_settings;
function __construct( $option_name, $default_settings = array() ){
$this->option_name = $option_name;
$this->default_settings = $default_settings;
$this->values = get_option( $this->option_name, $this->default_settings );
}
function __get( $key ){
if ( isset( $this->values[ $key ] ) ) {
return $this->values[ $key ];
}
}
function __set( $key, $value ){
$this->values[ $key ] = $value;
}
function __isset( $key ){
return isset( $this->values[ $key ] );
}
function get_option_name() {
return $this->option_name;
}
function save(){
update_option( $this->option_name, $this->values );
}
}
endif;

@ -3,7 +3,7 @@
Plugin Name: OpenID Connect - Generic Client
Plugin URI: https://github.com/daggerhart/openid-connect-generic
Description: Connect to an OpenID Connect identity provider with Authorization Code Flow
Version: 2.1
Version: 3.0
Author: daggerhart
Author URI: http://www.daggerhart.com
License: GPLv2 Copyright (c) 2015 daggerhart
@ -35,679 +35,109 @@ Notes
*/
define( 'OPENID_CONNECT_GENERIC_DIR', dirname( __FILE__ ) );
define( 'OPENID_CONNECT_GENERIC_SETTINGS_NAME', 'openid_connect_generic_settings' );
class OpenID_Connect_Generic {
private $cookie_id_key = 'openid-connect-generic-identity';
// states are only valid for 3 minutes
private $state_time_limit = 180;
// default plugin settings values
private $default_settings = array(
'login_type' => 'button',
'no_sslverify' => 0,
'enforce_privacy' => 0,
'identity_key' => 'sub',
'enable_logging' => 0,
'log_limit' => 1000,
);
// storage for plugin settings
private $settings = array();
// storage for error messages
private $errors = array();
private $redirect_uri;
private $logs = NULL;
/**
* Initialize the plugin
*/
function __construct() {
add_action( 'init', array( $this, 'init' ) );
$this->redirect_uri = admin_url( 'admin-ajax.php?action=openid-connect-authorize' );
// translatable errors
$this->errors = array(
1 => __( 'Cannot get authentication response' ),
2 => __( 'Cannot get token response' ),
3 => __( 'Cannot get user claims' ),
4 => __( 'Cannot get valid token' ),
5 => __( 'Cannot get user key' ),
6 => __( 'Cannot create authorized user' ),
7 => __( 'User not found' ),
8 => __( 'You do not have access to this site' ),
9 => __( 'Cannot get authorization to join this site' ),
99 => __( 'Unknown error' )
);
}
/**
* Get plugin settings
* - settings field logic in admin/settings class
*
* @return array
*/
public function get_settings() {
if ( ! empty( $this->settings ) ) {
return $this->settings;
}
class OpenID_Connect_Generic {
$this->settings = wp_parse_args( get_option( OPENID_CONNECT_GENERIC_SETTINGS_NAME, array() ), $this->default_settings );
// plugin settings
private $settings;
return $this->settings;
}
// plugin logs
private $logger;
/**
* Retrieve all log messages
*/
public function get_logs() {
if ( is_null( $this->logs ) ) {
$this->logs = get_option( 'openid_connect_generic_logs', array() );
}
// openid connect generic client
private $client;
return $this->logs;
}
// settings admin page
private $settings_page;
/**
*
*/
public function log( $data, $type = 'error' ) {
if ( (bool) $this->settings['enable_logging'] ) {
$this->add_log_message( $data, $type );
}
}
// login form adjustments
private $login_form;
/**
* Add a new message to the log
* Setup the plugin
*
* @param $data array - extra data about the message
* @param $type string - simple message type string, defaults to error
*/
public function add_log_message( $data = array(), $type = 'error' ) {
// construct our message
$message = array(
'type' => $type,
'time' => time(),
'user_ID' => get_current_user_id(),
'data' => is_array( $data ) ? $data : array( $data ),
);
// add our message to the logs
$logs = $this->get_logs();
$logs[] = $message;
// keep our log count under the limit
$items_to_remove = count( $logs ) - (int) $this->settings['log_limit'];
while ( $items_to_remove > 0 ) {
array_shift( $logs );
$items_to_remove --;
}
// save our logs
$this->logs = $logs;
update_option( 'openid_connect_generic_logs', $logs, FALSE );
}
/**
* Implements hook init
* - hook plugin into WP as needed
*/
public function init() {
// check the user's status based on plugin settings
$this->check_user_status();
// remove cookies on logout
add_action( 'wp_logout', array( $this, 'wp_logout' ) );
// verify legitimacy of user token on admin pages
add_action( 'admin_init', array( $this, 'check_user_token' ) );
// alter the login form as dictated by settings
add_filter( 'login_message', array( $this, 'login_message' ), 99 );
// alter the requests according to settings
add_filter( 'openid-connect-generic-alter-request', array(
$this,
'alter_request'
), 10, 3 );
// administration yo!
if ( is_admin() ) {
// use the ajax url to handle processing authorization without any html output
// this callback will occur when then IDP returns with an authenticated value
add_action( 'wp_ajax_openid-connect-authorize', array(
$this,
'auth_callback'
) );
add_action( 'wp_ajax_nopriv_openid-connect-authorize', array(
$this,
'auth_callback'
) );
// initialize the settings page
require_once OPENID_CONNECT_GENERIC_DIR . '/admin/openid-connect-generic-settings.php';
new OpenID_Connect_Generic_Settings( $this->get_settings() );
}
}
/**
* Validate the user's status based on plugin settings
*/
function check_user_status() {
$settings = $this->get_settings();
// check if privacy enforcement is enabled
if ( $settings['enforce_privacy'] &&
! is_user_logged_in() &&
// avoid redirects on cron or ajax
( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) &&
( ! defined( 'DOING_CRON' ) || ! DOING_CRON )
) {
global $pagenow;
// avoid redirect loop
if ( $pagenow != 'wp-login.php' && ! isset( $_GET['loggedout'] ) && ! isset( $_GET['login-error'] ) ) {
wp_redirect( wp_login_url() );
exit;
}
}
// verify token for any logged in user
if ( is_user_logged_in() ) {
$this->check_user_token();
}
}
/**
* Check the user's cookie
*/
function check_user_token() {
$is_openid_connect_user = get_user_meta( wp_get_current_user()->ID, 'openid-connect-generic-user', TRUE );
if ( is_user_logged_in() && ! empty( $is_openid_connect_user ) && ! isset( $_COOKIE[ $this->cookie_id_key ] ) ) {
wp_logout();
wp_redirect( wp_login_url() );
exit;
}
}
/**
* Control the authentication and subsequent authorization of the user when
* returning from the IDP.
* @param \WP_Option_Settings $settings
* @param \WP_Option_Logger $logger
*/
function auth_callback() {
$settings = $this->get_settings();
// look for an existing error of some kind
if ( isset( $_GET['error'] ) ) {
$this->error( 99 );
}
// make sure we have a legitimate authentication code and valid state
if ( ! isset( $_GET['code'] ) || ! isset( $_GET['state'] ) || ! $this->check_state( $_GET['state'] ) ) {
$this->error( 1 );
}
// we have an authorization code, make sure it is good by
// attempting to exchange it for an authentication token
$token_result = $this->request_authentication_token( $_GET['code'] );
// ensure the token is not an error generated by wp
if ( is_wp_error( $token_result ) ) {
$this->error( 2, $token_result );
}
// extract token response from token
$token_response = json_decode( $token_result['body'], TRUE );
// we need to ensure 3 specific items exist with the token response in order
// to proceed with confidence: id_token, access_token, and token_type == 'Bearer'
if ( ! isset( $token_response['id_token'] ) || ! isset( $token_response['access_token'] ) ||
! isset( $token_response['token_type'] ) || $token_response['token_type'] !== 'Bearer'
) {
$this->error( 4 );
}
// - end authentication
// - start authorization
// The id_token is used to identify the authenticated user, e.g. for SSO.
// The access_token must be used to prove access rights to protected resources
// e.g. for the userinfo endpoint
// break apart the id_token int eh response for decoding
$tmp = explode( '.', $token_response['id_token'] );
// Extract the id_token's claims from the token
$id_token_claim = json_decode( base64_decode( $tmp[1] ), TRUE );
// make sure we can find our identification data and that it has a value
if ( ! isset( $id_token_claim[ $settings['identity_key'] ] ) || empty( $id_token_claim[ $settings['identity_key'] ] ) ) {
$this->error( 5 );
}
// if desired, admins can use regex to determine if the identity value is valid
// according to their own standards expectations
if ( isset( $settings['allowed_regex'] ) && ! empty( $settings['allowed_regex'] ) &&
preg_match( $settings['allowed_regex'], $id_token_claim[ $settings['identity_key'] ] ) !== 1
) {
$this->error( 5 );
}
// send a userinfo request to get user claim
$user_claim_result = $this->request_userinfo( $token_response['access_token'] );
// make sure we didn't get an error, and that the response body exists
if ( is_wp_error( $user_claim_result ) || ! isset( $user_claim_result['body'] ) ) {
$this->error( 3, $user_claim_result );
}
$user_claim = json_decode( $user_claim_result['body'], TRUE );
// make sure the id_token sub === user_claim sub, according to spec
if ( $id_token_claim[ $settings['identity_key'] ] !== $user_claim['sub'] ) {
$this->error( 4 );
}
// retrieve the identity from the id_token
$user_identity = $id_token_claim[ $settings['identity_key'] ];
// - end authorization
// - start user handling
// allow plugins / themes to halt the login process early
// based on the user_claim
$login_user = apply_filters( 'openid-connect-generic-user-login-test', TRUE, $user_claim );
if ( ! $login_user ) {
$this->error( 8 );
}
// look for user by their openid-connect-generic-user-identity value
$user_query = new WP_User_Query( array(
'meta_query' => array(
array(
'key' => 'openid-connect-generic-user-identity',
'value' => $user_identity,
)
)
) );
// if we found an existing users, grab the first one returned
if ( $user_query->get_total() > 0 ) {
$users = $user_query->get_results();
$user = $users[0];
} // otherwise, user does not exist and we'll need to create it
else {
// default username & email to the user identity, since that is the only
// thing we can be sure to have
$username = $user_identity;
$email = $user_identity;
// allow claim details to determine username
if ( isset( $user_claim['email'] ) ) {
$email = $user_claim['email'];
$username = $this->get_username_from_claim( $user_claim );
} // if no name exists, attempt another request for userinfo
else if ( isset( $token_response['access_token'] ) ) {
$user_claim_result = $this->request_userinfo( $token_response['access_token'] );
// make sure we didn't get an error
if ( is_wp_error( $user_claim_result ) ) {
$this->error( 3, $user_claim_result );
}
$user_claim = json_decode( $user_claim_result['body'], TRUE );
if ( isset( $user_claim['email'] ) ) {
$email = $user_claim['email'];
$username = $this->get_username_from_claim( $user_claim );
}
}
// allow other plugins / themes to determine authorization
// of new accounts based on the returned user claim
$create_user = apply_filters( 'openid-connect-generic-user-creation-test', TRUE, $user_claim );
if ( ! $create_user ) {
$this->error( 9 );
}
// create the new user
$uid = wp_create_user( $username, wp_generate_password( 32, TRUE, TRUE ), $email );
// make sure we didn't fail in creating the user
if ( is_wp_error( $uid ) ) {
$this->error( 6, $uid );
}
$this->log( array(
'message' => 'New user created: ' . $uid
), 'success' );
$user = get_user_by( 'id', $uid );
// save some meta data about this new user for the future
add_user_meta( $user->ID, 'openid-connect-generic-user', TRUE, TRUE );
add_user_meta( $user->ID, 'openid-connect-generic-user-identity', (string) $user_identity, TRUE );
// allow plugins / themes to take action on new user creation
do_action( 'openid-connect-generic-user-create', $user, $user_claim );
}
// ensure our found user is a real WP_User
if ( ! is_a( $user, 'WP_User' ) || ! $user->exists() ) {
$this->error( 7, $user );
}
// hey, we made it!
// let's remember the tokens for future reference
update_user_meta( $user->ID, 'openid-connect-generic-last-token-response', $token_response );
update_user_meta( $user->ID, 'openid-connect-generic-last-id-token-claim', $id_token_claim );
update_user_meta( $user->ID, 'openid-connect-generic-last-user-claim', $user_claim );
// save our authorization cookie for the response expiration
$oauth_expiry = $token_response['expires_in'] + current_time( 'timestamp', TRUE );
setcookie( $this->cookie_id_key, $user_identity, $oauth_expiry, COOKIEPATH, COOKIE_DOMAIN, TRUE );
// get a cookie and go home!
wp_set_auth_cookie( $user->ID, FALSE );
$this->log( array(
'message' => "Successful login for: {$user->user_login} ({$user->ID})"
), 'success' );
wp_redirect( home_url() );
// - end user handling
function __construct( WP_Option_Settings $settings, WP_Option_Logger $logger ){
$this->settings = $settings;
$this->logger = $logger;
}
/**
* Using the authorization_code, request an authentication token from the idp
*
* @param $code - authorization_code
*
* @return array|\WP_Error
* WP Hook 'init'
*/
function request_authentication_token( $code ) {
$settings = $this->get_settings();
$request = array(
'body' => array(
'code' => $code,
'client_id' => $settings['client_id'],
'client_secret' => $settings['client_secret'],
'redirect_uri' => $this->redirect_uri,
'grant_type' => 'authorization_code',
'scope' => $settings['scope'],
)
function init(){
$this->client = new OpenID_Connect_Generic_Client(
$this->settings->client_id,
$this->settings->client_secret,
$this->settings->scope,
$this->settings->ep_login,
$this->settings->ep_userinfo,
$this->settings->ep_token,
// redirect uri
admin_url( 'admin-ajax.php?action=openid-connect-authorize' )
);
// allow modifications to the request
$request = apply_filters( 'openid-connect-generic-alter-request', $request, $settings, 'get-authentication-token' );
// call the server and ask for a token
$response = wp_remote_post( $settings['ep_token'], $request );
return $response;
}
/**
* Using an access_token, request the userinfo from the idp
*
* @param $access_token
*
* @return array|\WP_Error
*/
function request_userinfo( $access_token ) {
$settings = $this->get_settings();
// allow modifications to the request
$request = apply_filters( 'openid-connect-generic-alter-request', array(), $settings, 'get-userinfo' );
// attempt the request
$response = wp_remote_get( $settings['ep_userinfo'] . '?access_token=' . $access_token, $request );
return $response;
}
$this->client_wrapper = OpenID_Connect_Generic_Client_Wrapper::register( $this->client, $this->settings, $this->logger );
$this->login_form = OpenID_Connect_Generic_Login_Form::register( $this->settings, $this->client_wrapper );
/**
* Modify outgoing requests according to settings
*
* @param $request
* @param $settings
* @param $op
*
* @return mixed
*/
function alter_request( $request, $settings, $op ) {
if ( isset( $settings['no_sslverify'] ) && $settings['no_sslverify'] ) {
$request['sslverify'] = FALSE;
if ( is_admin() ){
$this->settings_page = OpenID_Connect_Generic_Settings_Page::register( $this->settings, $this->logger );
}
return $request;
}
/**
* Create a single use authentication url
*
* @return string
*/
function make_authentication_url() {
$settings = $this->get_settings();
$url = sprintf( '%1$s?response_type=code&scope=%2$s&client_id=%3$s&state=%4$s&redirect_uri=%5$s',
$settings['ep_login'],
urlencode( $settings['scope'] ),
urlencode( $settings['client_id'] ),
$this->new_state(),
urlencode( $this->redirect_uri )
);
return $url;
}
/**
* Generate a new state,
* save it to the states option with a timestamp,
* and return it.
*
* @return string
*/
function new_state() {
$states = get_option( 'openid-connect-generic-valid-states', array() );
// new state w/ timestamp
$new_state = md5( mt_rand() );
$states[ $new_state ] = time();
// save state
update_option( 'openid-connect-generic-valid-states', $states );
return $new_state;
}
/**
* Check the validity of a given state
*
* @param $state
* Autoloader
*
* @return bool
* @param $class
*/
function check_state( $state ) {
$states = get_option( 'openid-connect-generic-valid-states', array() );
$valid = FALSE;
static public function autoload( $class ) {
$filename = strtolower( str_replace( '_', '-', $class ) ) . '.php';
// remove any expired states
foreach ( $states as $code => $timestamp ) {
if ( ( $timestamp + $this->state_time_limit ) < time() ) {
unset( $states[ $code ] );
}
if ( file_exists( OPENID_CONNECT_GENERIC_DIR . '/includes/' . $filename ) ) {
require OPENID_CONNECT_GENERIC_DIR . '/includes/' . $filename;
}
// 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;
}
// save our altered states
update_option( 'openid-connect-generic-valid-states', $states );
return $valid;
}
/**
* Implements filter login_message
*
* @param $message
*
* @return string
* Instantiate the plugin and hook into WP
*/
function login_message( $message ) {
$settings = $this->get_settings();
static public function bootstrap(){
spl_autoload_register( array( 'OpenID_Connect_Generic', 'autoload' ) );
// errors and auto login can't happen at the same time
if ( isset( $_GET['login-error'] ) ) {
$message = $this->get_error_message( $_GET['login-error'] );
}
else if ( $settings['login_type'] == 'auto' ) {
wp_redirect( $this->make_authentication_url() );
exit;
}
// login button is appended to existing messages in case of error
if ( $settings['login_type'] == 'button' ) {
$message .= $this->login_button();
}
return $message;
}
$settings = new WP_Option_Settings(
'openid_connect_generic_settings',
// default settings values
array(
// oauth client settings
'client_id' => '',
'client_secret' => '',
'scope' => '',
'ep_login' => '',
'ep_userinfo' => '',
'ep_token' => '',
/**
* Handle errors by redirecting the user to the login form
* along with an error code
*
* @param $error_number - required
*/
function error( $error_number ) {
$args = func_get_args();
$error_number = array_shift( $args );
$url = wp_login_url() . '?login-error=' . $error_number;
// non-standard settings
'no_sslverify' => 0,
'identity_key' => 'sub',
'allowed_regex' => '',
$error = array(
'error id' => $error_number,
'error message' => $this->errors[ $error_number ],
// plugin settings
'login_type' => 'button',
'enforce_privacy' => 0,
'enable_logging' => 0,
'log_limit' => 1000,
)
);
// allow for additional error details
if ( ! empty( $args ) ) {
$error['details'] = $args;
}
// attempt to log the error
$this->log( $error );
// redirect user back to login page
wp_redirect( $url );
exit;
}
/**
* Display an error message to the user
*
* @param $error_number
*
* @return string
*/
function get_error_message( $error_number ) {
// fallback to unknown error
if ( ! isset( $this->errors[ $error_number ] ) ) {
$error_number = 99;
}
ob_start();
?>
<div id="login_error"><?php print $this->errors[ $error_number ]; ?></div>
<?php
return ob_get_clean();
}
/**
* Create a login button (link)
*
* @return string
*/
function login_button() {
$text = apply_filters( 'openid-connect-generic-login-button-text', __( 'Login with OpenID Connect' ) );
$href = $this->make_authentication_url();
ob_start();
?>
<div class="openid-connect-login-button" style="margin: 1em 0; text-align: center;">
<a class="button button-large" href="<?php print esc_url( $href ); ?>"><?php print $text; ?></a>
</div>
<?php
return ob_get_clean();
}
/**
* Implements hook wp_logout
*
* Remove cookies
*/
function wp_logout() {
setcookie( $this->cookie_id_key, '1', 0, COOKIEPATH, COOKIE_DOMAIN, TRUE );
}
/**
* Avoid user_login collisions by incrementing
*
* @param $user_claim array
*
* @return string
*/
function get_username_from_claim( $user_claim ) {
if ( isset( $user_claim['preferred_username'] ) && ! empty( $user_claim['preferred_username'] ) ) {
$desired_username = $user_claim['preferred_username'];
}
else if ( isset( $user_claim['name'] ) && ! empty( $user_claim['name'] ) ) {
$desired_username = $user_claim['name'];
}
else if ( isset( $user_claim['email'] ) && ! empty( $user_claim['email'] ) ) {
$tmp = explode( '@', $user_claim['email'] );
$desired_username = $tmp[0];
}
else {
// nothing to build a name from
return FALSE;
}
$logger = new WP_Option_Logger( 'openid-connect-generic-logs', 'error', $settings->enable_logging, $settings->log_limit );
// normalize the data a bit
$desired_username = strtolower( preg_replace( '/[^a-zA-Z\_0-9]/', '', $desired_username ) );
// copy the username for incrementing
$username = $desired_username;
// original user gets "name"
// second user gets "name2"
// etc
$count = 1;
while ( username_exists( $username ) ) {
$count ++;
$username = $desired_name . $count;
}
$plugin = new self( $settings, $logger );
return $username;
add_action( 'init', array( $plugin, 'init' ) );
}
}
new OpenID_Connect_Generic();
OpenID_Connect_Generic::bootstrap();
Loading…
Cancel
Save