modified plugin OpenID Connect Generic version 3.10.0

This commit is contained in:
2024-10-09 12:44:43 +00:00
committed by Gitium
parent 65c751c1d9
commit cd379e1d95
25 changed files with 5154 additions and 0 deletions

View File

@ -0,0 +1,30 @@
<?php
/**
* Global OIDCG functions.
*
* @package OpenID_Connect_Generic
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* Return a single use authentication URL.
*
* @return string
*/
function oidcg_get_authentication_url() {
return \OpenID_Connect_Generic::instance()->client_wrapper->get_authentication_url();
}
/**
* Refresh a user claim and update the user metadata.
*
* @param WP_User $user The user object.
* @param array $token_response The token response.
*
* @return WP_Error|array
*/
function oidcg_refresh_user_claim( $user, $token_response ) {
return \OpenID_Connect_Generic::instance()->client_wrapper->refresh_user_claim( $user, $token_response );
}

View File

@ -0,0 +1,568 @@
<?php
/**
* Plugin OIDC/oAuth client class.
*
* @package OpenID_Connect_Generic
* @category Authentication
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenID_Connect_Generic_Client class.
*
* Plugin OIDC/oAuth client class.
*
* @package OpenID_Connect_Generic
* @category Authentication
*/
class OpenID_Connect_Generic_Client {
/**
* The OIDC/oAuth client ID.
*
* @see OpenID_Connect_Generic_Option_Settings::client_id
*
* @var string
*/
private $client_id;
/**
* The OIDC/oAuth client secret.
*
* @see OpenID_Connect_Generic_Option_Settings::client_secret
*
* @var string
*/
private $client_secret;
/**
* The OIDC/oAuth scopes.
*
* @see OpenID_Connect_Generic_Option_Settings::scope
*
* @var string
*/
private $scope;
/**
* The OIDC/oAuth authorization endpoint URL.
*
* @see OpenID_Connect_Generic_Option_Settings::endpoint_login
*
* @var string
*/
private $endpoint_login;
/**
* The OIDC/oAuth User Information endpoint URL.
*
* @see OpenID_Connect_Generic_Option_Settings::endpoint_userinfo
*
* @var string
*/
private $endpoint_userinfo;
/**
* The OIDC/oAuth token validation endpoint URL.
*
* @see OpenID_Connect_Generic_Option_Settings::endpoint_token
*
* @var string
*/
private $endpoint_token;
/**
* The login flow "ajax" endpoint URI.
*
* @see OpenID_Connect_Generic_Option_Settings::redirect_uri
*
* @var string
*/
private $redirect_uri;
/**
* The specifically requested authentication contract at the IDP
*
* @see OpenID_Connect_Generic_Option_Settings::acr_values
*
* @var string
*/
private $acr_values;
/**
* The state time limit. States are only valid for 3 minutes.
*
* @see OpenID_Connect_Generic_Option_Settings::state_time_limit
*
* @var int
*/
private $state_time_limit = 180;
/**
* The logger object instance.
*
* @var OpenID_Connect_Generic_Option_Logger
*/
private $logger;
/**
* Client constructor.
*
* @param string $client_id @see OpenID_Connect_Generic_Option_Settings::client_id for description.
* @param string $client_secret @see OpenID_Connect_Generic_Option_Settings::client_secret for description.
* @param string $scope @see OpenID_Connect_Generic_Option_Settings::scope for description.
* @param string $endpoint_login @see OpenID_Connect_Generic_Option_Settings::endpoint_login for description.
* @param string $endpoint_userinfo @see OpenID_Connect_Generic_Option_Settings::endpoint_userinfo for description.
* @param string $endpoint_token @see OpenID_Connect_Generic_Option_Settings::endpoint_token for description.
* @param string $redirect_uri @see OpenID_Connect_Generic_Option_Settings::redirect_uri for description.
* @param string $acr_values @see OpenID_Connect_Generic_Option_Settings::acr_values for description.
* @param int $state_time_limit @see OpenID_Connect_Generic_Option_Settings::state_time_limit for description.
* @param OpenID_Connect_Generic_Option_Logger $logger The plugin logging object instance.
*/
public function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $acr_values, $state_time_limit, $logger ) {
$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;
$this->acr_values = $acr_values;
$this->state_time_limit = $state_time_limit;
$this->logger = $logger;
}
/**
* Provides the configured Redirect URI supplied to the IDP.
*
* @return string
*/
public function get_redirect_uri() {
return $this->redirect_uri;
}
/**
* Provide the configured IDP endpoint login URL.
*
* @return string
*/
public function get_endpoint_login_url() {
return $this->endpoint_login;
}
/**
* Validate the request for login authentication
*
* @param array<string> $request The authentication request results.
*
* @return array<string>|WP_Error
*/
public 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'] ) ) {
do_action( 'openid-connect-generic-no-state-provided' );
return new WP_Error( 'missing-state', __( 'Missing state.', 'daggerhart-openid-connect-generic' ), $request );
}
if ( ! $this->check_state( $request['state'] ) ) {
return new WP_Error( 'invalid-state', __( 'Invalid state.', 'daggerhart-openid-connect-generic' ), $request );
}
return $request;
}
/**
* Get the authorization code from the request
*
* @param array<string>|WP_Error $request The authentication request results.
*
* @return string|WP_Error
*/
public function get_authentication_code( $request ) {
if ( ! isset( $request['code'] ) ) {
return new WP_Error( 'missing-authentication-code', __( 'Missing authentication code.', 'daggerhart-openid-connect-generic' ), $request );
}
return $request['code'];
}
/**
* Using the authorization_code, request an authentication token from the IDP.
*
* @param string|WP_Error $code The authorization code.
*
* @return array<mixed>|WP_Error
*/
public function request_authentication_token( $code ) {
// Add Host header - required for when the openid-connect endpoint is behind a reverse-proxy.
$parsed_url = parse_url( $this->endpoint_token );
$host = $parsed_url['host'];
$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,
),
'headers' => array( 'Host' => $host ),
);
if ( ! empty( $this->acr_values ) ) {
$request['body'] += array( 'acr_values' => $this->acr_values );
}
// 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.
$start_time = microtime( true );
$response = wp_remote_post( $this->endpoint_token, $request );
$end_time = microtime( true );
$this->logger->log( $this->endpoint_token, 'request_authentication_token', $end_time - $start_time );
if ( is_wp_error( $response ) ) {
$response->add( 'request_authentication_token', __( 'Request for authentication token failed.', 'daggerhart-openid-connect-generic' ) );
}
return $response;
}
/**
* Using the refresh token, request new tokens from the idp
*
* @param string $refresh_token The refresh token previously obtained from token response.
*
* @return array|WP_Error
*/
public function request_new_tokens( $refresh_token ) {
$request = array(
'body' => array(
'refresh_token' => $refresh_token,
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'grant_type' => 'refresh_token',
),
);
// Allow modifications to the request.
$request = apply_filters( 'openid-connect-generic-alter-request', $request, 'refresh-token' );
// Call the server and ask for new tokens.
$start_time = microtime( true );
$response = wp_remote_post( $this->endpoint_token, $request );
$end_time = microtime( true );
$this->logger->log( $this->endpoint_token, 'request_new_tokens', $end_time - $start_time );
if ( is_wp_error( $response ) ) {
$response->add( 'refresh_token', __( 'Refresh token failed.', 'daggerhart-openid-connect-generic' ) );
}
return $response;
}
/**
* Extract and decode the token body of a token response
*
* @param array<mixed>|WP_Error $token_result The token response.
*
* @return array<mixed>|WP_Error|null
*/
public function get_token_response( $token_result ) {
if ( ! isset( $token_result['body'] ) ) {
return new WP_Error( 'missing-token-body', __( 'Missing token body.', 'daggerhart-openid-connect-generic' ), $token_result );
}
// Extract the token response from token.
$token_response = json_decode( $token_result['body'], true );
// Check that the token response body was able to be parsed.
if ( is_null( $token_response ) ) {
return new WP_Error( 'invalid-token', __( 'Invalid token.', 'daggerhart-openid-connect-generic' ), $token_result );
}
if ( isset( $token_response['error'] ) ) {
$error = $token_response['error'];
$error_description = $error;
if ( isset( $token_response['error_description'] ) ) {
$error_description = $token_response['error_description'];
}
return new WP_Error( $error, $error_description, $token_result );
}
return $token_response;
}
/**
* Exchange an access_token for a user_claim from the userinfo endpoint
*
* @param string $access_token The access token supplied from authentication user claim.
*
* @return array|WP_Error
*/
public function request_userinfo( $access_token ) {
// Allow modifications to the request.
$request = apply_filters( 'openid-connect-generic-alter-request', array(), 'get-userinfo' );
/*
* Section 5.3.1 of the spec recommends sending the access token using the authorization header
* a filter may or may not have already added headers - make sure they exist then add the token.
*/
if ( ! array_key_exists( 'headers', $request ) || ! is_array( $request['headers'] ) ) {
$request['headers'] = array();
}
$request['headers']['Authorization'] = 'Bearer ' . $access_token;
// Add Host header - required for when the openid-connect endpoint is behind a reverse-proxy.
$parsed_url = parse_url( $this->endpoint_userinfo );
$host = $parsed_url['host'];
if ( ! empty( $parsed_url['port'] ) ) {
$host .= ":{$parsed_url['port']}";
}
$request['headers']['Host'] = $host;
// Attempt the request including the access token in the query string for backwards compatibility.
$start_time = microtime( true );
$response = wp_remote_post( $this->endpoint_userinfo, $request );
$end_time = microtime( true );
$this->logger->log( $this->endpoint_userinfo, 'request_userinfo', $end_time - $start_time );
if ( is_wp_error( $response ) ) {
$response->add( 'request_userinfo', __( 'Request for userinfo failed.', 'daggerhart-openid-connect-generic' ) );
}
return $response;
}
/**
* Generate a new state, save it as a transient, and return the state hash.
*
* @param string $redirect_to The redirect URL to be used after IDP authentication.
*
* @return string
*/
public function new_state( $redirect_to ) {
// New state w/ timestamp.
$state = md5( mt_rand() . microtime( true ) );
$state_value = array(
$state => array(
'redirect_to' => $redirect_to,
),
);
set_transient( 'openid-connect-generic-state--' . $state, $state_value, $this->state_time_limit );
return $state;
}
/**
* Check the existence of a given state transient.
*
* @param string $state The state hash to validate.
*
* @return bool
*/
public function check_state( $state ) {
$state_found = true;
if ( ! get_option( '_transient_openid-connect-generic-state--' . $state ) ) {
do_action( 'openid-connect-generic-state-not-found', $state );
$state_found = false;
}
$valid = get_transient( 'openid-connect-generic-state--' . $state );
if ( ! $valid && $state_found ) {
do_action( 'openid-connect-generic-state-expired', $state );
}
return boolval( $valid );
}
/**
* Get the authorization state from the request
*
* @param array<string>|WP_Error $request The authentication request results.
*
* @return string|WP_Error
*/
public function get_authentication_state( $request ) {
if ( ! isset( $request['state'] ) ) {
return new WP_Error( 'missing-authentication-state', __( 'Missing authentication state.', 'daggerhart-openid-connect-generic' ), $request );
}
return $request['state'];
}
/**
* Ensure that the token meets basic requirements.
*
* @param array $token_response The token response.
*
* @return bool|WP_Error
*/
public function validate_token_response( $token_response ) {
/*
* Ensure 2 specific items exist with the token response in order
* to proceed with confidence: id_token and token_type == 'Bearer'
*/
if ( ! isset( $token_response['id_token'] ) ||
! isset( $token_response['token_type'] ) || strcasecmp( $token_response['token_type'], 'Bearer' )
) {
return new WP_Error( 'invalid-token-response', 'Invalid token response', $token_response );
}
return true;
}
/**
* Extract the id_token_claim from the token_response.
*
* @param array $token_response The token response.
*
* @return array|WP_Error
*/
public function get_id_token_claim( $token_response ) {
// Validate there is an id_token.
if ( ! isset( $token_response['id_token'] ) ) {
return new WP_Error( 'no-identity-token', __( 'No identity token.', 'daggerhart-openid-connect-generic' ), $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( 'missing-identity-token', __( 'Missing identity token.', 'daggerhart-openid-connect-generic' ), $token_response );
}
// Extract the id_token's claims from the token.
$id_token_claim = json_decode(
base64_decode(
str_replace( // Because token is encoded in base64 URL (and not just base64).
array( '-', '_' ),
array( '+', '/' ),
$tmp[1]
)
),
true
);
return $id_token_claim;
}
/**
* Ensure the id_token_claim contains the required values.
*
* @param array $id_token_claim The ID token claim.
*
* @return bool|WP_Error
*/
public function validate_id_token_claim( $id_token_claim ) {
if ( ! is_array( $id_token_claim ) ) {
return new WP_Error( 'bad-id-token-claim', __( 'Bad ID token claim.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
// Validate the identification data and it's value.
if ( ! isset( $id_token_claim['sub'] ) || empty( $id_token_claim['sub'] ) ) {
return new WP_Error( 'no-subject-identity', __( 'No subject identity.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
// Validate acr values when the option is set in the configuration.
if ( ! empty( $this->acr_values ) && isset( $id_token_claim['acr'] ) ) {
if ( $this->acr_values != $id_token_claim['acr'] ) {
return new WP_Error( 'no-match-acr', __( 'No matching acr values.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
}
return true;
}
/**
* Attempt to exchange the access_token for a user_claim.
*
* @param array $token_response The token response.
*
* @return array|WP_Error|null
*/
public 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.', 'daggerhart-openid-connect-generic' ), $user_claim_result );
}
$user_claim = json_decode( $user_claim_result['body'], true );
return $user_claim;
}
/**
* Make sure the user_claim has all required values, and that the subject
* identity matches of the id_token matches that of the user_claim.
*
* @param array $user_claim The authenticated user claim.
* @param array $id_token_claim The ID token claim.
*
* @return bool|WP_Error
*/
public function validate_user_claim( $user_claim, $id_token_claim ) {
// Validate the user claim.
if ( ! is_array( $user_claim ) ) {
return new WP_Error( 'invalid-user-claim', __( 'Invalid user claim.', 'daggerhart-openid-connect-generic' ), $user_claim );
}
// Allow for errors from the IDP.
if ( isset( $user_claim['error'] ) ) {
$message = __( 'Error from the IDP.', 'daggerhart-openid-connect-generic' );
if ( ! empty( $user_claim['error_description'] ) ) {
$message = $user_claim['error_description'];
}
return new WP_Error( 'invalid-user-claim-' . $user_claim['error'], $message, $user_claim );
}
// Make sure the id_token sub equals the user_claim sub, according to spec.
if ( $id_token_claim['sub'] !== $user_claim['sub'] ) {
return new WP_Error( 'incorrect-user-claim', __( 'Incorrect user claim.', 'daggerhart-openid-connect-generic' ), 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 access.', 'daggerhart-openid-connect-generic' ), $login_user );
}
return true;
}
/**
* Retrieve the subject identity from the id_token.
*
* @param array $id_token_claim The ID token claim.
*
* @return mixed
*/
public function get_subject_identity( $id_token_claim ) {
return $id_token_claim['sub'];
}
}

View File

@ -0,0 +1,178 @@
<?php
/**
* Login form and login button handling class.
*
* @package OpenID_Connect_Generic
* @category Login
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenID_Connect_Generic_Login_Form class.
*
* Login form and login button handling.
*
* @package OpenID_Connect_Generic
* @category Login
*/
class OpenID_Connect_Generic_Login_Form {
/**
* Plugin settings object.
*
* @var OpenID_Connect_Generic_Option_Settings
*/
private $settings;
/**
* Plugin client wrapper instance.
*
* @var OpenID_Connect_Generic_Client_Wrapper
*/
private $client_wrapper;
/**
* The class constructor.
*
* @param OpenID_Connect_Generic_Option_Settings $settings A plugin settings object instance.
* @param OpenID_Connect_Generic_Client_Wrapper $client_wrapper A plugin client wrapper object instance.
*/
public function __construct( $settings, $client_wrapper ) {
$this->settings = $settings;
$this->client_wrapper = $client_wrapper;
}
/**
* Create an instance of the OpenID_Connect_Generic_Login_Form class.
*
* @param OpenID_Connect_Generic_Option_Settings $settings A plugin settings object instance.
* @param OpenID_Connect_Generic_Client_Wrapper $client_wrapper A plugin client wrapper object instance.
*
* @return void
*/
public static 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 );
// Add a shortcode for the login button.
add_shortcode( 'openid_connect_generic_login_button', array( $login_form, 'make_login_button' ) );
$login_form->handle_redirect_login_type_auto();
}
/**
* Auto Login redirect.
*
* @return void
*/
public function handle_redirect_login_type_auto() {
if ( 'wp-login.php' == $GLOBALS['pagenow']
&& ( 'auto' == $this->settings->login_type || ! empty( $_GET['force_redirect'] ) )
// Don't send users to the IDP on logout or post password protected authentication.
&& ( ! isset( $_GET['action'] ) || ! in_array( $_GET['action'], array( 'logout', 'postpass' ) ) )
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- WP Login Form doesn't have a nonce.
&& ! isset( $_POST['wp-submit'] ) ) {
if ( ! isset( $_GET['login-error'] ) ) {
wp_redirect( $this->client_wrapper->get_authentication_url() );
exit;
} else {
add_action( 'login_footer', array( $this, 'remove_login_form' ), 99 );
}
}
}
/**
* Implements filter login_message.
*
* @param string $message The text message to display on the login page.
*
* @return string
*/
public function handle_login_page( $message ) {
if ( isset( $_GET['login-error'] ) ) {
$error_message = ! empty( $_GET['message'] ) ? sanitize_text_field( wp_unslash( $_GET['message'] ) ) : 'Unknown error.';
$message .= $this->make_error_output( sanitize_text_field( wp_unslash( $_GET['login-error'] ) ), $error_message );
}
// Login button is appended to existing messages in case of error.
$message .= $this->make_login_button();
return $message;
}
/**
* Display an error message to the user.
*
* @param string $error_code The error code.
* @param string $error_message The error message test.
*
* @return string
*/
public function make_error_output( $error_code, $error_message ) {
ob_start();
?>
<div id="login_error"><?php // translators: %1$s is the error code from the IDP. ?>
<strong><?php printf( esc_html__( 'ERROR (%1$s)', 'daggerhart-openid-connect-generic' ), esc_html( $error_code ) ); ?>: </strong>
<?php print esc_html( $error_message ); ?>
</div>
<?php
return wp_kses_post( ob_get_clean() );
}
/**
* Create a login button (link).
*
* @param array $atts Array of optional attributes to override login buton
* functionality when used by shortcode.
*
* @return string
*/
public function make_login_button( $atts = array() ) {
$atts = shortcode_atts(
array(
'button_text' => __( 'Login with OpenID Connect', 'daggerhart-openid-connect-generic' ),
),
$atts,
'openid_connect_generic_login_button'
);
$text = apply_filters( 'openid-connect-generic-login-button-text', $atts['button_text'] );
$text = esc_html( $text );
$href = $this->client_wrapper->get_authentication_url( $atts );
$href = esc_url_raw( $href );
$login_button = <<<HTML
<div class="openid-connect-login-button" style="margin: 1em 0; text-align: center;">
<a class="button button-large" href="{$href}">{$text}</a>
</div>
HTML;
return $login_button;
}
/**
* Removes the login form from the HTML DOM
*
* @return void
*/
public function remove_login_form() {
?>
<script type="text/javascript">
(function() {
var loginForm = document.getElementById("user_login").form;
var parent = loginForm.parentNode;
parent.removeChild(loginForm);
})();
</script>
<?php
}
}

View File

@ -0,0 +1,266 @@
<?php
/**
* Plugin logging class.
*
* @package OpenID_Connect_Generic
* @category Logging
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2023 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenID_Connect_Generic_Option_Logger class.
*
* Simple class for logging messages to the options table.
*
* @package OpenID_Connect_Generic
* @category Logging
*/
class OpenID_Connect_Generic_Option_Logger {
/**
* Thw WordPress option name/key.
*
* @var string
*/
const OPTION_NAME = 'openid-connect-generic-logs';
/**
* The default message type.
*
* @var string
*/
private $default_message_type = 'none';
/**
* The number of items to keep in the log.
*
* @var int
*/
private $log_limit = 1000;
/**
* Whether or not logging is enabled.
*
* @var bool
*/
private $logging_enabled = true;
/**
* Internal cache of logs.
*
* @var array
*/
private $logs;
/**
* Setup the logger according to the needs of the instance.
*
* @param string|null $default_message_type The log message type.
* @param bool|TRUE|null $logging_enabled Whether logging is enabled.
* @param int|null $log_limit The log entry limit.
*/
public function __construct( $default_message_type = null, $logging_enabled = null, $log_limit = null ) {
if ( ! is_null( $default_message_type ) ) {
$this->default_message_type = $default_message_type;
}
if ( ! is_null( $logging_enabled ) ) {
$this->logging_enabled = boolval( $logging_enabled );
}
if ( ! is_null( $log_limit ) ) {
$this->log_limit = intval( $log_limit );
}
}
/**
* Save an array of data to the logs.
*
* @param string|array<string, string>|WP_Error $data The log message data.
* @param string|null $type The log message type.
* @param float|null $processing_time Optional event processing time.
* @param int|null $time The log message timestamp (default: time()).
* @param int|null $user_ID The current WordPress user ID (default: get_current_user_id()).
* @param string|null $request_uri The related HTTP request URI (default: $_SERVER['REQUEST_URI']|'Unknown').
*
* @return bool
*/
public function log( $data, $type = null, $processing_time = null, $time = null, $user_ID = null, $request_uri = null ) {
if ( boolval( $this->logging_enabled ) ) {
$logs = $this->get_logs();
$logs[] = $this->make_message( $data, $type, $processing_time, $time, $user_ID, $request_uri );
$logs = $this->upkeep_logs( $logs );
return $this->save_logs( $logs );
}
return false;
}
/**
* Retrieve all log messages.
*
* @return array
*/
public function get_logs() {
if ( empty( $this->logs ) ) {
$this->logs = get_option( self::OPTION_NAME, array() );
}
// Call the upkeep_logs function to give the appearance that logs have been reduced to the $this->log_limit.
// The logs are actually limited during a logging action but the logger isn't available during a simple settings update.
return $this->upkeep_logs( $this->logs );
}
/**
* Get the name of the option where this log is stored.
*
* @return string
*/
public function get_option_name() {
return self::OPTION_NAME;
}
/**
* Create a message array containing the data and other information.
*
* @param string|array<string, string>|WP_Error $data The log message data.
* @param string|null $type The log message type.
* @param float|null $processing_time Optional event processing time.
* @param int|null $time The log message timestamp (default: time()).
* @param int|null $user_ID The current WordPress user ID (default: get_current_user_id()).
* @param string|null $request_uri The related HTTP request URI (default: $_SERVER['REQUEST_URI']|'Unknown').
*
* @return array
*/
private function make_message( $data, $type, $processing_time, $time, $user_ID, $request_uri ) {
// Determine the type of message.
if ( empty( $type ) ) {
$type = $this->default_message_type;
if ( is_array( $data ) && isset( $data['type'] ) ) {
$type = $data['type'];
unset( $data['type'] );
}
if ( is_wp_error( $data ) ) {
$type = $data->get_error_code();
$data = $data->get_error_message( $type );
}
}
if ( empty( $request_uri ) ) {
$request_uri = ( ! empty( $_SERVER['REQUEST_URI'] ) ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : 'Unknown';
$request_uri = preg_replace( '/code=([^&]+)/i', 'code=', $request_uri );
}
// Construct the message.
$message = array(
'type' => $type,
'time' => ! empty( $time ) ? $time : time(),
'user_ID' => ! is_null( $user_ID ) ? $user_ID : get_current_user_id(),
'uri' => $request_uri,
'data' => $data,
'processing_time' => $processing_time,
);
return $message;
}
/**
* Keep the log count under the limit.
*
* @param array $logs The plugin logs.
*
* @return array
*/
private function upkeep_logs( $logs ) {
$items_to_remove = count( $logs ) - $this->log_limit;
if ( $items_to_remove > 0 ) {
// Only keep the last $log_limit messages from the end.
$logs = array_slice( $logs, $items_to_remove );
}
return $logs;
}
/**
* Save the log messages.
*
* @param array $logs The array of log messages.
*
* @return bool
*/
private function save_logs( $logs ) {
// Save the logs.
$this->logs = $logs;
return update_option( self::OPTION_NAME, $logs, false );
}
/**
* Clear all log messages.
*
* @return void
*/
public function clear_logs() {
$this->save_logs( array() );
}
/**
* Get a simple html table of all the logs.
*
* @param array $logs The array of log messages.
*
* @return string
*/
public function get_logs_table( $logs = array() ) {
if ( empty( $logs ) ) {
$logs = $this->get_logs();
}
$logs = array_reverse( $logs );
ini_set( 'xdebug.var_display_max_depth', '-1' );
ob_start();
?>
<table id="logger-table" class="wp-list-table widefat fixed striped posts">
<thead>
<th class="col-details"><?php esc_html_e( 'Details', 'daggerhart-openid-connect-generic' ); ?></th>
<th class="col-data"><?php esc_html_e( 'Data', 'daggerhart-openid-connect-generic' ); ?></th>
</thead>
<tbody>
<?php foreach ( $logs as $log ) { ?>
<tr>
<td class="col-details">
<div>
<label><?php esc_html_e( 'Date', 'daggerhart-openid-connect-generic' ); ?></label>
<?php print esc_html( ! empty( $log['time'] ) ? gmdate( 'Y-m-d H:i:s', $log['time'] ) : '' ); ?>
</div>
<div>
<label><?php esc_html_e( 'Type', 'daggerhart-openid-connect-generic' ); ?></label>
<?php print esc_html( ! empty( $log['type'] ) ? $log['type'] : '' ); ?>
</div>
<div>
<label><?php esc_html_e( 'User', 'daggerhart-openid-connect-generic' ); ?>: </label>
<?php print esc_html( ( get_userdata( $log['user_ID'] ) ) ? get_userdata( $log['user_ID'] )->user_login : '0' ); ?>
</div>
<div>
<label><?php esc_html_e( 'URI ', 'daggerhart-openid-connect-generic' ); ?>: </label>
<?php print esc_url( ! empty( $log['uri'] ) ? $log['uri'] : '' ); ?>
</div>
<div>
<label><?php esc_html_e( 'Response&nbsp;Time&nbsp;(sec)', 'daggerhart-openid-connect-generic' ); ?></label>
<?php print esc_html( ! empty( $log['response_time'] ) ? $log['response_time'] : '' ); ?>
</div>
</td>
<td class="col-data"><pre><?php var_dump( ! empty( $log['data'] ) ? $log['data'] : '' ); ?></pre></td>
</tr>
<?php } ?>
</tbody>
</table>
<?php
$output = ob_get_clean();
return $output;
}
}

View File

@ -0,0 +1,213 @@
<?php
/**
* WordPress options handling class.
*
* @package OpenID_Connect_Generic
* @category Settings
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2023 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenId_Connect_Generic_Option_Settings class.
*
* WordPress options handling.
*
* @package OpenID_Connect_Generic
* @category Settings
*
* Legacy Settings:
*
* @property string $ep_login The login endpoint.
* @property string $ep_token The token endpoint.
* @property string $ep_userinfo The userinfo endpoint.
*
* OAuth Client Settings:
*
* @property string $login_type How the client (login form) should provide login options.
* @property string $client_id The ID the client will be recognized as when connecting the to Identity provider server.
* @property string $client_secret The secret key the IDP server expects from the client.
* @property string $scope The list of scopes this client should access.
* @property string $endpoint_login The IDP authorization endpoint URL.
* @property string $endpoint_userinfo The IDP User information endpoint URL.
* @property string $endpoint_token The IDP token validation endpoint URL.
* @property string $endpoint_end_session The IDP logout endpoint URL.
* @property string $acr_values The Authentication contract as defined on the IDP.
*
* Non-standard Settings:
*
* @property bool $no_sslverify The flag to enable/disable SSL verification during authorization.
* @property int $http_request_timeout The timeout for requests made to the IDP. Default value is 5.
* @property string $identity_key The key in the user claim array to find the user's identification data.
* @property string $nickname_key The key in the user claim array to find the user's nickname.
* @property string $email_format The key(s) in the user claim array to formulate the user's email address.
* @property string $displayname_format The key(s) in the user claim array to formulate the user's display name.
* @property bool $identify_with_username The flag which indicates how the user's identity will be determined.
* @property int $state_time_limit The valid time limit of the state, in seconds. Defaults to 180 seconds.
*
* Plugin Settings:
*
* @property bool $enforce_privacy The flag to indicates whether a user us required to be authenticated to access the site.
* @property bool $alternate_redirect_uri The flag to indicate whether to use the alternative redirect URI.
* @property bool $token_refresh_enable The flag whether to support refresh tokens by IDPs.
* @property bool $link_existing_users The flag to indicate whether to link to existing WordPress-only accounts or greturn an error.
* @property bool $create_if_does_not_exist The flag to indicate whether to create new users or not.
* @property bool $redirect_user_back The flag to indicate whether to redirect the user back to the page on which they started.
* @property bool $redirect_on_logout The flag to indicate whether to redirect to the login screen on session expiration.
* @property bool $enable_logging The flag to enable/disable logging.
* @property int $log_limit The maximum number of log entries to keep.
*/
class OpenID_Connect_Generic_Option_Settings {
/**
* WordPress option name/key.
*
* @var string
*/
const OPTION_NAME = 'openid_connect_generic_settings';
/**
* Stored option values array.
*
* @var array<mixed>
*/
private $values;
/**
* Default plugin settings values.
*
* @var array<mixed>
*/
private $default_settings;
/**
* List of settings that can be defined by environment variables.
*
* @var array<string,string>
*/
private $environment_settings = array(
'client_id' => 'OIDC_CLIENT_ID',
'client_secret' => 'OIDC_CLIENT_SECRET',
'endpoint_end_session' => 'OIDC_ENDPOINT_LOGOUT_URL',
'endpoint_login' => 'OIDC_ENDPOINT_LOGIN_URL',
'endpoint_token' => 'OIDC_ENDPOINT_TOKEN_URL',
'endpoint_userinfo' => 'OIDC_ENDPOINT_USERINFO_URL',
'login_type' => 'OIDC_LOGIN_TYPE',
'scope' => 'OIDC_CLIENT_SCOPE',
'create_if_does_not_exist' => 'OIDC_CREATE_IF_DOES_NOT_EXIST',
'enforce_privacy' => 'OIDC_ENFORCE_PRIVACY',
'link_existing_users' => 'OIDC_LINK_EXISTING_USERS',
'redirect_on_logout' => 'OIDC_REDIRECT_ON_LOGOUT',
'redirect_user_back' => 'OIDC_REDIRECT_USER_BACK',
'acr_values' => 'OIDC_ACR_VALUES',
'enable_logging' => 'OIDC_ENABLE_LOGGING',
'log_limit' => 'OIDC_LOG_LIMIT',
);
/**
* The class constructor.
*
* @param array<mixed> $default_settings The default plugin settings values.
* @param bool $granular_defaults The granular defaults.
*/
public function __construct( $default_settings = array(), $granular_defaults = true ) {
$this->default_settings = $default_settings;
$this->values = array();
$this->values = (array) get_option( self::OPTION_NAME, $this->default_settings );
// For each defined environment variable/constant be sure the settings key is set.
foreach ( $this->environment_settings as $key => $constant ) {
if ( defined( $constant ) ) {
$this->__set( $key, constant( $constant ) );
}
}
if ( $granular_defaults ) {
$this->values = array_replace_recursive( $this->default_settings, $this->values );
}
}
/**
* Magic getter for settings.
*
* @param string $key The array key/option name.
*
* @return mixed
*/
public function __get( $key ) {
if ( isset( $this->values[ $key ] ) ) {
return $this->values[ $key ];
}
}
/**
* Magic setter for settings.
*
* @param string $key The array key/option name.
* @param mixed $value The option value.
*
* @return void
*/
public function __set( $key, $value ) {
$this->values[ $key ] = $value;
}
/**
* Magic method to check is an attribute isset.
*
* @param string $key The array key/option name.
*
* @return bool
*/
public function __isset( $key ) {
return isset( $this->values[ $key ] );
}
/**
* Magic method to clear an attribute.
*
* @param string $key The array key/option name.
*
* @return void
*/
public function __unset( $key ) {
unset( $this->values[ $key ] );
}
/**
* Get the plugin settings array.
*
* @return array
*/
public function get_values() {
return $this->values;
}
/**
* Get the plugin WordPress options name.
*
* @return string
*/
public function get_option_name() {
return self::OPTION_NAME;
}
/**
* Save the plugin options to the WordPress options table.
*
* @return void
*/
public function save() {
// For each defined environment variable/constant be sure it isn't saved to the database.
foreach ( $this->environment_settings as $key => $constant ) {
if ( defined( $constant ) ) {
$this->__unset( $key );
}
}
update_option( self::OPTION_NAME, $this->values );
}
}

View File

@ -0,0 +1,603 @@
<?php
/**
* Plugin Admin settings page class.
*
* @package OpenID_Connect_Generic
* @category Settings
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2023 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenID_Connect_Generic_Settings_Page class.
*
* Admin settings page.
*
* @package OpenID_Connect_Generic
* @category Settings
*/
class OpenID_Connect_Generic_Settings_Page {
/**
* Local copy of the settings provided by the base plugin.
*
* @var OpenID_Connect_Generic_Option_Settings
*/
private $settings;
/**
* Instance of the plugin logger.
*
* @var OpenID_Connect_Generic_Option_Logger
*/
private $logger;
/**
* The controlled list of settings & associated defined during
* construction for i18n reasons.
*
* @var array
*/
private $settings_fields = array();
/**
* Options page slug.
*
* @var string
*/
private $options_page_name = 'openid-connect-generic-settings';
/**
* Options page settings group name.
*
* @var string
*/
private $settings_field_group;
/**
* Settings page class constructor.
*
* @param OpenID_Connect_Generic_Option_Settings $settings The plugin settings object.
* @param OpenID_Connect_Generic_Option_Logger $logger The plugin logging class object.
*/
public function __construct( OpenID_Connect_Generic_Option_Settings $settings, OpenID_Connect_Generic_Option_Logger $logger ) {
$this->settings = $settings;
$this->logger = $logger;
$this->settings_field_group = $this->settings->get_option_name() . '-group';
$fields = $this->get_settings_fields();
// Some simple pre-processing.
foreach ( $fields as $key => &$field ) {
$field['key'] = $key;
$field['name'] = $this->settings->get_option_name() . '[' . $key . ']';
}
// Allow alterations of the fields.
$this->settings_fields = $fields;
}
/**
* Hook the settings page into WordPress.
*
* @param OpenID_Connect_Generic_Option_Settings $settings A plugin settings object instance.
* @param OpenID_Connect_Generic_Option_Logger $logger A plugin logger object instance.
*
* @return void
*/
public static function register( OpenID_Connect_Generic_Option_Settings $settings, OpenID_Connect_Generic_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' ) );
}
/**
* Implements hook admin_menu to add our options/settings page to the
* dashboard menu.
*
* @return void
*/
public function admin_menu() {
add_options_page(
__( 'OpenID Connect - Generic Client', 'daggerhart-openid-connect-generic' ),
__( 'OpenID Connect Client', 'daggerhart-openid-connect-generic' ),
'manage_options',
$this->options_page_name,
array( $this, 'settings_page' )
);
}
/**
* Implements hook admin_init to register our settings.
*
* @return void
*/
public function admin_init() {
register_setting(
$this->settings_field_group,
$this->settings->get_option_name(),
array(
$this,
'sanitize_settings',
)
);
add_settings_section(
'client_settings',
__( 'Client Settings', 'daggerhart-openid-connect-generic' ),
array( $this, 'client_settings_description' ),
$this->options_page_name
);
add_settings_section(
'user_settings',
__( 'WordPress User Settings', 'daggerhart-openid-connect-generic' ),
array( $this, 'user_settings_description' ),
$this->options_page_name
);
add_settings_section(
'authorization_settings',
__( 'Authorization Settings', 'daggerhart-openid-connect-generic' ),
array( $this, 'authorization_settings_description' ),
$this->options_page_name
);
add_settings_section(
'log_settings',
__( 'Log Settings', 'daggerhart-openid-connect-generic' ),
array( $this, 'log_settings_description' ),
$this->options_page_name
);
// 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;
}
// Determine appropriate output callback.
switch ( $field['type'] ) {
case 'checkbox':
$callback = 'do_checkbox';
break;
case 'select':
$callback = 'do_select';
break;
case 'text':
default:
$callback = 'do_text_field';
break;
}
// Add the field.
add_settings_field(
$key,
$field['title'],
array( $this, $callback ),
$this->options_page_name,
$field['section'],
$field
);
}
}
/**
* Get the plugin settings fields definition.
*
* @return array
*/
private function get_settings_fields() {
/**
* Simple settings fields have:
*
* - title
* - description
* - type ( checkbox | text | select )
* - section - settings/option page section ( client_settings | authorization_settings )
* - example (optional example will appear beneath description and be wrapped in <code>)
*/
$fields = array(
'login_type' => array(
'title' => __( 'Login Type', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Select how the client (login form) should provide login options.', 'daggerhart-openid-connect-generic' ),
'type' => 'select',
'options' => array(
'button' => __( 'OpenID Connect button on login form', 'daggerhart-openid-connect-generic' ),
'auto' => __( 'Auto Login - SSO', 'daggerhart-openid-connect-generic' ),
),
'disabled' => defined( 'OIDC_LOGIN_TYPE' ),
'section' => 'client_settings',
),
'client_id' => array(
'title' => __( 'Client ID', 'daggerhart-openid-connect-generic' ),
'description' => __( 'The ID this client will be recognized as when connecting the to Identity provider server.', 'daggerhart-openid-connect-generic' ),
'example' => 'my-wordpress-client-id',
'type' => 'text',
'disabled' => defined( 'OIDC_CLIENT_ID' ),
'section' => 'client_settings',
),
'client_secret' => array(
'title' => __( 'Client Secret Key', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Arbitrary secret key the server expects from this client. Can be anything, but should be very unique.', 'daggerhart-openid-connect-generic' ),
'type' => 'text',
'disabled' => defined( 'OIDC_CLIENT_SECRET' ),
'section' => 'client_settings',
),
'scope' => array(
'title' => __( 'OpenID Scope', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Space separated list of scopes this client should access.', 'daggerhart-openid-connect-generic' ),
'example' => 'email profile openid offline_access',
'type' => 'text',
'disabled' => defined( 'OIDC_CLIENT_SCOPE' ),
'section' => 'client_settings',
),
'endpoint_login' => array(
'title' => __( 'Login Endpoint URL', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Identify provider authorization endpoint.', 'daggerhart-openid-connect-generic' ),
'example' => 'https://example.com/oauth2/authorize',
'type' => 'text',
'disabled' => defined( 'OIDC_ENDPOINT_LOGIN_URL' ),
'section' => 'client_settings',
),
'endpoint_userinfo' => array(
'title' => __( 'Userinfo Endpoint URL', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Identify provider User information endpoint.', 'daggerhart-openid-connect-generic' ),
'example' => 'https://example.com/oauth2/UserInfo',
'type' => 'text',
'disabled' => defined( 'OIDC_ENDPOINT_USERINFO_URL' ),
'section' => 'client_settings',
),
'endpoint_token' => array(
'title' => __( 'Token Validation Endpoint URL', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Identify provider token endpoint.', 'daggerhart-openid-connect-generic' ),
'example' => 'https://example.com/oauth2/token',
'type' => 'text',
'disabled' => defined( 'OIDC_ENDPOINT_TOKEN_URL' ),
'section' => 'client_settings',
),
'endpoint_end_session' => array(
'title' => __( 'End Session Endpoint URL', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Identify provider logout endpoint.', 'daggerhart-openid-connect-generic' ),
'example' => 'https://example.com/oauth2/logout',
'type' => 'text',
'disabled' => defined( 'OIDC_ENDPOINT_LOGOUT_URL' ),
'section' => 'client_settings',
),
'acr_values' => array(
'title' => __( 'ACR values', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Use a specific defined authentication contract from the IDP - optional.', 'daggerhart-openid-connect-generic' ),
'type' => 'text',
'disabled' => defined( 'OIDC_ACR_VALUES' ),
'section' => 'client_settings',
),
'identity_key' => array(
'title' => __( 'Identity Key', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Where in the user claim array to find the user\'s identification data. Possible standard values: preferred_username, name, or sub. If you\'re having trouble, use "sub".', 'daggerhart-openid-connect-generic' ),
'example' => 'preferred_username',
'type' => 'text',
'section' => 'client_settings',
),
'no_sslverify' => array(
'title' => __( 'Disable SSL Verify', 'daggerhart-openid-connect-generic' ),
// translators: %1$s HTML tags for layout/styles, %2$s closing HTML tag for styles.
'description' => sprintf( __( 'Do not require SSL verification during authorization. The OAuth extension uses curl to make the request. By default CURL will generally verify the SSL certificate to see if its valid an issued by an accepted CA. This setting disabled that verification.%1$sNot recommended for production sites.%2$s', 'daggerhart-openid-connect-generic' ), '<br><strong>', '</strong>' ),
'type' => 'checkbox',
'section' => 'client_settings',
),
'http_request_timeout' => array(
'title' => __( 'HTTP Request Timeout', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Set the timeout for requests made to the IDP. Default value is 5.', 'daggerhart-openid-connect-generic' ),
'example' => 30,
'type' => 'text',
'section' => 'client_settings',
),
'enforce_privacy' => array(
'title' => __( 'Enforce Privacy', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Require users be logged in to see the site.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'disabled' => defined( 'OIDC_ENFORCE_PRIVACY' ),
'section' => 'authorization_settings',
),
'alternate_redirect_uri' => array(
'title' => __( 'Alternate Redirect URI', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Provide an alternative redirect route. Useful if your server is causing issues with the default admin-ajax method. You must flush rewrite rules after changing this setting. This can be done by saving the Permalinks settings page.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'section' => 'authorization_settings',
),
'nickname_key' => array(
'title' => __( 'Nickname Key', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Where in the user claim array to find the user\'s nickname. Possible standard values: preferred_username, name, or sub.', 'daggerhart-openid-connect-generic' ),
'example' => 'preferred_username',
'type' => 'text',
'section' => 'client_settings',
),
'email_format' => array(
'title' => __( 'Email Formatting', 'daggerhart-openid-connect-generic' ),
'description' => __( 'String from which the user\'s email address is built. Specify "{email}" as long as the user claim contains an email claim.', 'daggerhart-openid-connect-generic' ),
'example' => '{email}',
'type' => 'text',
'section' => 'client_settings',
),
'displayname_format' => array(
'title' => __( 'Display Name Formatting', 'daggerhart-openid-connect-generic' ),
'description' => __( 'String from which the user\'s display name is built.', 'daggerhart-openid-connect-generic' ),
'example' => '{given_name} {family_name}',
'type' => 'text',
'section' => 'client_settings',
),
'identify_with_username' => array(
'title' => __( 'Identify with User Name', 'daggerhart-openid-connect-generic' ),
'description' => __( 'If checked, the user\'s identity will be determined by the user name instead of the email address.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'section' => 'client_settings',
),
'state_time_limit' => array(
'title' => __( 'State time limit', 'daggerhart-openid-connect-generic' ),
'description' => __( 'State valid time in seconds. Defaults to 180', 'daggerhart-openid-connect-generic' ),
'type' => 'number',
'section' => 'client_settings',
),
'token_refresh_enable' => array(
'title' => __( 'Enable Refresh Token', 'daggerhart-openid-connect-generic' ),
'description' => __( 'If checked, support refresh tokens used to obtain access tokens from supported IDPs.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'section' => 'client_settings',
),
'link_existing_users' => array(
'title' => __( 'Link Existing Users', 'daggerhart-openid-connect-generic' ),
'description' => __( 'If a WordPress account already exists with the same identity as a newly-authenticated user over OpenID Connect, login as that user instead of generating an error.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'disabled' => defined( 'OIDC_LINK_EXISTING_USERS' ),
'section' => 'user_settings',
),
'create_if_does_not_exist' => array(
'title' => __( 'Create user if does not exist', 'daggerhart-openid-connect-generic' ),
'description' => __( 'If the user identity is not linked 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 linked to an existing WordPress user, then the authentication will fail.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'disabled' => defined( 'OIDC_CREATE_IF_DOES_NOT_EXIST' ),
'section' => 'user_settings',
),
'redirect_user_back' => array(
'title' => __( 'Redirect Back to Origin Page', 'daggerhart-openid-connect-generic' ),
'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.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'disabled' => defined( 'OIDC_REDIRECT_USER_BACK' ),
'section' => 'user_settings',
),
'redirect_on_logout' => array(
'title' => __( 'Redirect to the login screen when session is expired', 'daggerhart-openid-connect-generic' ),
'description' => __( 'When enabled, this will automatically redirect the user back to the WordPress login page if their access token has expired.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'disabled' => defined( 'OIDC_REDIRECT_ON_LOGOUT' ),
'section' => 'user_settings',
),
'enable_logging' => array(
'title' => __( 'Enable Logging', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Very simple log messages for debugging purposes.', 'daggerhart-openid-connect-generic' ),
'type' => 'checkbox',
'disabled' => defined( 'OIDC_ENABLE_LOGGING' ),
'section' => 'log_settings',
),
'log_limit' => array(
'title' => __( 'Log Limit', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Number of items to keep in the log. These logs are stored as an option in the database, so space is limited.', 'daggerhart-openid-connect-generic' ),
'type' => 'number',
'disabled' => defined( 'OIDC_LOG_LIMIT' ),
'section' => 'log_settings',
),
);
return apply_filters( 'openid-connect-generic-settings-fields', $fields );
}
/**
* Sanitization callback for settings/option page.
*
* @param array $input The submitted settings values.
*
* @return array
*/
public function sanitize_settings( $input ) {
$options = array();
// Loop through settings fields to control what we're saving.
foreach ( $this->settings_fields as $key => $field ) {
if ( isset( $input[ $key ] ) ) {
$options[ $key ] = sanitize_text_field( trim( $input[ $key ] ) );
} else {
$options[ $key ] = '';
}
}
return $options;
}
/**
* Output the options/settings page.
*
* @return void
*/
public function settings_page() {
wp_enqueue_style( 'daggerhart-openid-connect-generic-admin', plugin_dir_url( __DIR__ ) . 'css/styles-admin.css', array(), OpenID_Connect_Generic::VERSION, 'all' );
$redirect_uri = admin_url( 'admin-ajax.php?action=openid-connect-authorize' );
if ( $this->settings->alternate_redirect_uri ) {
$redirect_uri = site_url( '/openid-connect-authorize' );
}
?>
<div class="wrap">
<h2><?php print esc_html( get_admin_page_title() ); ?></h2>
<form method="post" action="options.php">
<?php
settings_fields( $this->settings_field_group );
do_settings_sections( $this->options_page_name );
submit_button();
// Simple debug to view settings array.
if ( isset( $_GET['debug'] ) ) {
var_dump( $this->settings->get_values() );
}
?>
</form>
<h4><?php esc_html_e( 'Notes', 'daggerhart-openid-connect-generic' ); ?></h4>
<p class="description">
<strong><?php esc_html_e( 'Redirect URI', 'daggerhart-openid-connect-generic' ); ?></strong>
<code><?php print esc_url( $redirect_uri ); ?></code>
</p>
<p class="description">
<strong><?php esc_html_e( 'Login Button Shortcode', 'daggerhart-openid-connect-generic' ); ?></strong>
<code>[openid_connect_generic_login_button]</code>
</p>
<p class="description">
<strong><?php esc_html_e( 'Authentication URL Shortcode', 'daggerhart-openid-connect-generic' ); ?></strong>
<code>[openid_connect_generic_auth_url]</code>
</p>
<?php if ( $this->settings->enable_logging ) { ?>
<h2><?php esc_html_e( 'Logs', 'daggerhart-openid-connect-generic' ); ?></h2>
<div id="logger-table-wrapper">
<?php print wp_kses_post( $this->logger->get_logs_table() ); ?>
</div>
<?php } ?>
</div>
<?php
}
/**
* Output a standard text field.
*
* @param array $field The settings field definition array.
*
* @return void
*/
public function do_text_field( $field ) {
?>
<input type="<?php print esc_attr( $field['type'] ); ?>"
id="<?php print esc_attr( $field['key'] ); ?>"
class="large-text<?php echo ( ! empty( $field['disabled'] ) && boolval( $field['disabled'] ) === true ) ? ' disabled' : ''; ?>"
name="<?php print esc_attr( $field['name'] ); ?>"
<?php echo ( ! empty( $field['disabled'] ) && boolval( $field['disabled'] ) === true ) ? ' disabled' : ''; ?>
value="<?php print esc_attr( $this->settings->{ $field['key'] } ); ?>">
<?php
$this->do_field_description( $field );
}
/**
* Output a checkbox for a boolean setting.
* - hidden field is default value so we don't have to check isset() on save.
*
* @param array $field The settings field definition array.
*
* @return void
*/
public function do_checkbox( $field ) {
$hidden_value = 0;
if ( ! empty( $field['disabled'] ) && boolval( $field['disabled'] ) === true ) {
$hidden_value = intval( $this->settings->{ $field['key'] } );
}
?>
<input type="hidden" name="<?php print esc_attr( $field['name'] ); ?>" value="<?php print esc_attr( strval( $hidden_value ) ); ?>">
<input type="checkbox"
id="<?php print esc_attr( $field['key'] ); ?>"
name="<?php print esc_attr( $field['name'] ); ?>"
<?php echo ( ! empty( $field['disabled'] ) && boolval( $field['disabled'] ) === true ) ? ' disabled="disabled"' : ''; ?>
value="1"
<?php checked( $this->settings->{ $field['key'] }, 1 ); ?>>
<?php
$this->do_field_description( $field );
}
/**
* Output a select control.
*
* @param array $field The settings field definition array.
*
* @return void
*/
public function do_select( $field ) {
$current_value = isset( $this->settings->{ $field['key'] } ) ? $this->settings->{ $field['key'] } : '';
?>
<select
id="<?php print esc_attr( $field['key'] ); ?>"
name="<?php print esc_attr( $field['name'] ); ?>"
<?php echo ( ! empty( $field['disabled'] ) && boolval( $field['disabled'] ) === true ) ? ' disabled' : ''; ?>
>
<?php foreach ( $field['options'] as $value => $text ) : ?>
<option value="<?php print esc_attr( $value ); ?>" <?php selected( $value, $current_value ); ?>><?php print esc_html( $text ); ?></option>
<?php endforeach; ?>
</select>
<?php
$this->do_field_description( $field );
}
/**
* Output the field description, and example if present.
*
* @param array $field The settings field definition array.
*
* @return void
*/
public function do_field_description( $field ) {
?>
<p class="description">
<?php print wp_kses_post( $field['description'] ); ?>
<?php if ( isset( $field['example'] ) ) : ?>
<br/><strong><?php esc_html_e( 'Example', 'daggerhart-openid-connect-generic' ); ?>: </strong>
<code><?php print esc_html( $field['example'] ); ?></code>
<?php endif; ?>
</p>
<?php
}
/**
* Output the 'Client Settings' plugin setting section description.
*
* @return void
*/
public function client_settings_description() {
esc_html_e( 'Enter your OpenID Connect identity provider settings.', 'daggerhart-openid-connect-generic' );
}
/**
* Output the 'WordPress User Settings' plugin setting section description.
*
* @return void
*/
public function user_settings_description() {
esc_html_e( 'Modify the interaction between OpenID Connect and WordPress users.', 'daggerhart-openid-connect-generic' );
}
/**
* Output the 'Authorization Settings' plugin setting section description.
*
* @return void
*/
public function authorization_settings_description() {
esc_html_e( 'Control the authorization mechanics of the site.', 'daggerhart-openid-connect-generic' );
}
/**
* Output the 'Log Settings' plugin setting section description.
*
* @return void
*/
public function log_settings_description() {
esc_html_e( 'Log information about login attempts through OpenID Connect Generic.', 'daggerhart-openid-connect-generic' );
}
}