Add features for Isekai Book

isekai
落雨楓 4 years ago
parent a283a18fec
commit 715045b58e

@ -9,6 +9,7 @@
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
use MatthiasMullie\Minify\JS;
use \WP_Error as WP_Error;
/**
@ -122,6 +123,13 @@ class OpenID_Connect_Generic_Client_Wrapper {
add_action( 'wp_loaded', array( $client_wrapper, 'ensure_tokens_still_fresh' ) );
}
// Disable some profile settings
add_action( 'personal_options_update', array( $client_wrapper, 'disable_profile_change' ), 5 );
add_action( 'show_user_profile', array( $client_wrapper, 'show_user_profile' ), 0 );
// Avatar
add_filter( 'get_avatar', array( $client_wrapper, 'get_avatar' ), 100, 5);
return $client_wrapper;
}
@ -238,6 +246,76 @@ class OpenID_Connect_Generic_Client_Wrapper {
return apply_filters( 'openid-connect-generic-auth-url', $url );
}
public function disable_profile_change( $user_id ) {
$user = get_user_by('id', $user_id );
$_POST['email'] = $user->user_email;
// $_POST['nickname'] = $user->user_nickname;
}
public function show_user_profile( $user ) {
if ( !empty( $this->settings->profile_edit_url ) ) {
$profileUrl = $this->settings->profile_edit_url;
$clientId = $this->settings->client_id;
$currentUrl = admin_url( 'profile.php' );
if ( strpos( $profileUrl, '?' ) === false ) {
$profileUrl .= '?';
} else {
$profileUrl .= '&';
}
$profileUrl .= http_build_query( array(
'referrer' => $clientId,
'referrer_uri' => $currentUrl,
) );
?>
<script>
(function($) {
$(function() {
$('#email').prop('disabled', true);
$('#idp-profile').insertAfter($("#your-profile .form-table:first"));
$('#idp-profile-title').insertAfter($("#your-profile .form-table:first"));
$('#email-description').text('请在用户中心更改电子邮箱地址');
$('#password td').html('<p class="description">请在用户中心更改密码</p>');
$('#your-profile .user-profile-picture .description').hide();
});
})(jQuery);
</script>
<h2 id="idp-profile-title">全局用户资料</h2>
<table class="form-table" role="presentation" id="idp-profile">
<tbody>
<tr id="idp-profile" class="user-pass1-wrap">
<th><label>更改用户资料</label></th>
<td>
<a type="button" class="button" href="<?= $profileUrl ?>" aria-expanded="false">前往用户中心</a>
</td>
</tr>
</tbody>
</table>
<?php
}
}
public function get_avatar( $avatar, $id_or_email, $size, $default, $alt ) {
if ( is_numeric( $id_or_email ) ) {
$id = (int) $id_or_email;
$user = get_userdata( $id );
} elseif ( is_object( $id_or_email ) ) {
if ( isset( $id_or_email->comment_type ) && '' != $id_or_email->comment_type && 'comment' != $id_or_email->comment_type )
return false;
if ( !empty( $id_or_email->user_id ) ) {
$id = (int) $id_or_email->user_id;
$user = get_userdata( $id );
}
}
if ($user) {
$avatarSrc = get_user_meta( $user->ID , 'openid-connect-generic-avatar', true );
if ( !empty($avatarSrc) ) {
return "<img alt='{$alt}' src='{$avatarSrc}' class='avatar avatar-{$size} photo avatar-default' height='{$size}' width='{$size}' />";
}
}
return false;
}
/**
* Handle retrieval and validation of refresh_token.
*
@ -358,35 +436,12 @@ class OpenID_Connect_Generic_Client_Wrapper {
$url .= $query ? '&' : '?';
// Prevent redirect back to the IDP when logging out in auto mode.
if ( 'auto' === $this->settings->login_type && strpos( $redirect_url, 'wp-login.php?loggedout=true' ) ) {
if ( 'auto' === $this->settings->login_type && strpos( $redirect_url, 'wp-login.php' ) ) {
// By default redirect back to the site home.
$redirect_url = home_url();
}
$token_response = $user->get( 'openid-connect-generic-last-token-response' );
if ( ! $token_response ) {
// Happens if non-openid login was used.
return $redirect_url;
} else if ( ! parse_url( $redirect_url, PHP_URL_HOST ) ) {
// Convert to absolute url if needed, site_url() to be friendly with non-standard (Bedrock) layout.
$redirect_url = site_url( $redirect_url );
}
$claim = $user->get( 'openid-connect-generic-last-id-token-claim' );
if ( isset( $claim['iss'] ) && 'https://accounts.google.com' == $claim['iss'] ) {
/*
* Google revoke endpoint
* 1. expects the *access_token* to be passed as "token"
* 2. does not support redirection (post_logout_redirect_uri)
* So just redirect to regular WP logout URL.
* (we would *not* disconnect the user from any Google service even
* if he was initially disconnected to them)
*/
return $redirect_url;
} else {
return $url . sprintf( 'id_token_hint=%s&post_logout_redirect_uri=%s', $token_response['id_token'], urlencode( $redirect_url ) );
}
return $url . 'redirect_uri=' . urlencode($redirect_url);
}
/**
@ -522,6 +577,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
} else {
// Allow plugins / themes to take action using current claims on existing user (e.g. update role).
do_action( 'openid-connect-generic-update-user-using-current-claim', $user, $user_claim );
$this->update_user_profile( $user, $user_claim );
}
// Validate the found / created user.
@ -597,7 +653,29 @@ class OpenID_Connect_Generic_Client_Wrapper {
update_user_meta( $user->ID, 'openid-connect-generic-last-user-claim', $user_claim );
// Create the WP session, so we know its token.
$expiration = time() + apply_filters( 'auth_cookie_expiration', 2 * DAY_IN_SECONDS, $user->ID, false );
$remember = isset($id_token_claim['remember_me']) && $id_token_claim['remember_me'];
if ( $remember ) {
/**
* Filters the duration of the authentication cookie expiration period.
*
* @since 2.8.0
*
* @param int $length Duration of the expiration period in seconds.
* @param int $user_id User ID.
* @param bool $remember Whether to remember the user login. Default false.
*/
$expiration = time() + apply_filters( 'auth_cookie_expiration', 14 * DAY_IN_SECONDS, $user->ID, $remember );
/*
* Ensure the browser will continue to send the cookie after the expiration time is reached.
* Needed for the login grace period in wp_validate_auth_cookie().
*/
$expire = $expiration + ( 12 * HOUR_IN_SECONDS );
} else {
/** This filter is documented in wp-includes/pluggable.php */
$expiration = time() + apply_filters( 'auth_cookie_expiration', 2 * DAY_IN_SECONDS, $user->ID, $remember );
$expire = 0;
}
$manager = WP_Session_Tokens::get_instance( $user->ID );
$token = $manager->create( $expiration );
@ -605,7 +683,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
$this->save_refresh_token( $manager, $token, $token_response );
// you did great, have a cookie!
wp_set_auth_cookie( $user->ID, false, '', $token );
wp_set_auth_cookie( $user->ID, $remember, '', $token );
do_action( 'wp_login', $user->user_login, $user );
}
@ -831,6 +909,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
$email = $subject_identity;
$nickname = $subject_identity;
$displayname = $subject_identity;
$avatar = isset($user_claim['avatar']) ? $user_claim['avatar'] : '';
$values_missing = false;
// Allow claim details to determine username, email, nickname and displayname.
@ -912,6 +991,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
if ( $uid ) {
$user = $this->update_existing_user( $uid, $subject_identity );
do_action( 'openid-connect-generic-update-user-using-current-claim', $user, $user_claim );
$this->update_user_profile( $user, $user_claim );
return $user;
}
}
@ -932,8 +1012,10 @@ class OpenID_Connect_Generic_Client_Wrapper {
'user_email' => $email,
'display_name' => $displayname,
'nickname' => $nickname,
/*
'first_name' => isset( $user_claim['given_name'] ) ? $user_claim['given_name'] : '',
'last_name' => isset( $user_claim['family_name'] ) ? $user_claim['family_name'] : '',
*/
);
$user_data = apply_filters( 'openid-connect-generic-alter-user-data', $user_data, $user_claim );
@ -950,6 +1032,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
// Save some meta data about this new user for the future.
add_user_meta( $user->ID, 'openid-connect-generic-subject-identity', (string) $subject_identity, true );
add_user_meta( $user->ID, 'openid-connect-generic-avatar', $avatar );
// Log the results.
$this->logger->log( "New user created: {$user->user_login} ($uid)", 'success' );
@ -978,4 +1061,34 @@ class OpenID_Connect_Generic_Client_Wrapper {
// Return our updated user.
return get_user_by( 'id', $uid );
}
/**
* Update user profile from OpenID Connect Provider
*
* @param \WP_User $user The WordPress User ID.
* @param string $user_claim The user info from the IDP.
*/
public function update_user_profile( $user, $user_claim ) {
$avatar = isset($user_claim['avatar']) ? $user_claim['avatar'] : '';
update_user_meta( $user->ID, 'openid-connect-generic-avatar', $avatar );
$email = $user->user_email;
if ( isset($user_claim['email'] ) ) $email = $user_claim['email'];
$nickname = $user->nickname;
if ( isset($user_claim['name'] ) ) $nickname = $user_claim['name'];
$displayname = $user->display_name;
if ( $displayname === $user->nickname ) $displayname = $nickname;
$userData = [
'ID' => $user->ID,
'user_login' => $user->user_login,
'user_email' => $email,
'nickname' => $nickname,
'display_name' => $displayname
];
$userData = apply_filters( 'openid-connect-generic-alter-user-data', $userData, $user_claim );
$user = wp_insert_user( $userData );
}
}

@ -44,18 +44,22 @@
* @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.
*
* @property string $profile_edit_url The IDP account management console URL.
* @property string $realm The IDP realm.
*
* 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.
* @property bool $enable_webhook The flag whether receive webhook from IDPs.
* @property string $webhook_key The key for webhook authentication.
* @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 {

@ -305,27 +305,51 @@ class OpenID_Connect_Generic_Settings_Page {
'type' => 'checkbox',
'section' => 'authorization_settings',
),
'nickname_key' => array(
'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(
'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(
'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',
),
'profile_edit_url' => array(
'title' => __( 'Profile Management URL', 'daggerhart-openid-connect-generic' ),
'description' => __( 'For users to edit their profile on IDP.' ),
'type' => 'text',
'section' => 'client_settings',
),
'enable_webhook' => array(
'title' => __( 'Enable Webhook', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Allows receive webhook from IDP.' ),
'type' => 'checkbox',
'section' => 'client_settings',
),
'webhook_key' => array(
'title' => __( 'Webhook key', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Secret key to authentication webhook' ),
'type' => 'text',
'section' => 'client_settings',
),
'realm' => array(
'title' => __( 'Realm', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Identify provider realm, required when enabled webhook' ),
'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' ),
@ -419,6 +443,11 @@ class OpenID_Connect_Generic_Settings_Page {
if ( $this->settings->alternate_redirect_uri ) {
$redirect_uri = site_url( '/openid-connect-authorize' );
}
$webhook_endpoint = rest_url( '/openid-connect/v1/webhook' ) . '/[IDP type]';
if ( !empty($this->settings->webhook_key) ) {
$webhook_endpoint .= '?key=' . $this->settings->webhook_key;
}
?>
<div class="wrap">
<h2><?php print esc_html( get_admin_page_title() ); ?></h2>
@ -442,6 +471,20 @@ class OpenID_Connect_Generic_Settings_Page {
<strong><?php esc_html_e( 'Redirect URI', 'daggerhart-openid-connect-generic' ); ?></strong>
<code><?php print esc_url( $redirect_uri ); ?></code>
</p>
<?php
if ($this->settings->enable_webhook) {
?>
<p class="description">
<strong><?php esc_html_e( 'Webhook Endpoint', 'daggerhart-openid-connect-generic' ); ?></strong>
<code><?php print $webhook_endpoint; ?></code>
</p>
<p class="description">
<strong><?php esc_html_e( 'Webhook Supported IDP Type', 'daggerhart-openid-connect-generic' ); ?></strong>
<code>keycloak</code>
</p>
<?php
}
?>
<p class="description">
<strong><?php esc_html_e( 'Login Button Shortcode', 'daggerhart-openid-connect-generic' ); ?></strong>
<code>[openid_connect_generic_login_button]</code>

@ -0,0 +1,139 @@
<?php
class OpenID_Connect_Generic_Webhook {
/**
* The settings object instance.
*
* @var OpenID_Connect_Generic_Option_Settings
*/
private $settings;
/**
* Plugin client wrapper instance.
*
* @var OpenID_Connect_Generic_Client_Wrapper
*/
private $client_wrapper;
/**
* The logger object instance.
*
* @var OpenID_Connect_Generic_Option_Logger
*/
private $logger;
public function __construct(OpenID_Connect_Generic_Option_Settings $settings,
OpenID_Connect_Generic_Client_Wrapper $client_wrapper,
OpenID_Connect_Generic_Option_Logger $logger) {
$this->settings = $settings;
$this->client_wrapper = $client_wrapper;
$this->logger = $logger;
}
public static function register(OpenID_Connect_Generic_Option_Settings $settings,
OpenID_Connect_Generic_Client_Wrapper $client_wrapper,
OpenID_Connect_Generic_Option_Logger $logger) {
$webhook = new OpenID_Connect_Generic_Webhook($settings, $client_wrapper, $logger);
add_action('rest_api_init', [$webhook, 'register_rest_api']);
return $webhook;
}
public function register_rest_api() {
register_rest_route('openid-connect/v1', '/webhook/keycloak', [
[
'methods' => ['GET', 'POST'],
'callback' => [$this, 'webhook_keycloak'],
],
'schema' => [$this, 'webhook_keycloak_schema'],
]);
}
public function webhook_keycloak(WP_REST_Request $request) {
if (!$this->settings->enable_webhook) {
$this->logger->log('Webhook is disabled in setting.', 'webhook');
return rest_ensure_response([
'status' => -1,
'code' => 'ERR::WEBHOOK_DISABLED',
'error' => 'Webhook is disabled in setting.'
]);
}
$query = $request->get_query_params();
if (!empty($this->settings->webhook_key) && $query['key'] != $this->settings->webhook_key) {
$this->logger->log('Webhook key is incorrect.', 'webhook');
return rest_ensure_response([
'status' => -1,
'code' => 'ERR::WEBHOOK_KEY_INCORRECT',
'error' => 'Webhook key is incorrect.'
]);
}
$event = $request->get_json_params();
if (is_null($event)) {
$this->logger->log('Event body is empty.', 'webhook');
return rest_ensure_response([
'status' => -1,
'code' => 'ERR::EMPTY_EVENT_BODY',
'error' => 'Event body is empty.',
]);
}
if ($event['realmId'] != $this->settings->realm) {
$this->logger->log("Realm id mismatch: {$event['realmId']}", 'webhook');
return rest_ensure_response([
'status' => 0,
'warning' => "Realm id mismatch: {$event['realmId']}",
]);
}
if (!in_array($event['type'], ['UPDATE_PROFILE', 'UPDATE_EMAIL'])) {
return rest_ensure_response([
'status' => 0,
'warning' => 'Event type ignored',
]);
}
$subject = $event['userId'];
$user = $this->client_wrapper->get_user_by_identity($subject);
if (!$user) {
$this->logger->log("Cannot find user for subject: {$subject}", 'webhook');
return rest_ensure_response([
'status' => 0,
'warning' => "Cannot find user for subject: {$subject}",
]);
}
if (isset($event['userInfo'])) { // Update user profile
$this->client_wrapper->update_user_profile($user, $event['userInfo']);
$this->logger->log("Updated user profile: {$user->user_login}", 'webhook');
}
return rest_ensure_response([
'status' => 1
]);
}
public function webhook_keycloak_schema() {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
// The title property marks the identity of the resource.
'title' => 'keycloak-webhook',
'type' => 'object',
'required' => ['type', 'realmId', 'userId', 'userInfo'],
'properties' => [
'type' => [
'description' => esc_html__('Event Type', 'daggerhart-openid-connect-generic'),
'type' => 'string',
],
'realmId' => [
'description' => esc_html__('IDP Realm ID', 'daggerhart-openid-connect-generic'),
'type' => 'string',
],
'userId' => [
'description' => esc_html__('IDP User ID', 'daggerhart-openid-connect-generic'),
'type' => 'string',
],
'userInfo' => [
'description' => esc_html__('IDP User Info', 'daggerhart-openid-connect-generic'),
'type' => 'object',
],
],
];
}
}

@ -173,6 +173,8 @@ class OpenID_Connect_Generic {
if ( is_admin() ) {
OpenID_Connect_Generic_Settings_Page::register( $this->settings, $this->logger );
}
OpenID_Connect_Generic_Webhook::register( $this->settings, $this->client_wrapper, $this->logger );
}
/**
@ -342,10 +344,14 @@ class OpenID_Connect_Generic {
'identity_key' => 'preferred_username',
'nickname_key' => 'preferred_username',
'email_format' => '{email}',
'profile_edit_url' => '',
'displayname_format' => '',
'identify_with_username' => false,
'realm' => '',
// Plugin settings.
'enable_webhook' => false,
'webhook_key' => '',
'enforce_privacy' => 0,
'alternate_redirect_uri' => 0,
'token_refresh_enable' => 1,

Loading…
Cancel
Save