Initialize Project
commit
126c1a1337
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
$specialPageAliases = [];
|
||||||
|
|
||||||
|
$specialPageAliases['en'] = [
|
||||||
|
'IsekaiOIDCCallback' => ['IsekaiOIDCCallback'],
|
||||||
|
];
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "hyperzlib/isekai-oidc",
|
||||||
|
"type": "mediawiki-extension",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Hyperzlib",
|
||||||
|
"email": "hyperzlib@outlook.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"phpseclib/phpseclib" : "2.0.1"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"installer-name": "IsekaiOIDC"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "Isekai OpenID Connect",
|
||||||
|
"author": "hyperzlib",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"url": "https://www.isekai.cn",
|
||||||
|
"descriptionmsg": "ucenter-desc",
|
||||||
|
"license-name": "MIT",
|
||||||
|
"type": "other",
|
||||||
|
"requires": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"MessagesDirs": {
|
||||||
|
"IsekaiOIDC": [
|
||||||
|
"i18n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AutoloadNamespaces": {
|
||||||
|
"Isekai\\OIDC\\": "includes/"
|
||||||
|
},
|
||||||
|
"AutoloadClasses": {
|
||||||
|
"Jumbojett\\OpenIDConnectClient": "lib/openid-connect-php/OpenIDConnectClient.php",
|
||||||
|
"Jumbojett\\OpenIDConnectClientException": "lib/openid-connect-php/OpenIDConnectClient.php"
|
||||||
|
},
|
||||||
|
"callback": "Isekai\\OIDC\\IsekaiOIDCAuthHooks::onRegistration",
|
||||||
|
"Hooks": {
|
||||||
|
"LoadExtensionSchemaUpdates": "Isekai\\OIDC\\IsekaiOIDCAuthHooks::loadExtensionSchemaUpdates",
|
||||||
|
"BeforePageDisplay": "Isekai\\OIDC\\IsekaiOIDCAuthHooks::onBeforePageDisplay",
|
||||||
|
"UserLogoutComplete": "Isekai\\OIDC\\IsekaiOIDCAuthHooks::onLogout",
|
||||||
|
"GetPreferences": "Isekai\\OIDC\\IsekaiOIDCAuthHooks::onGetPreferences",
|
||||||
|
"PersonalUrls": "Isekai\\OIDC\\IsekaiOIDCAuthHooks::onPersonalUrls",
|
||||||
|
"ResourceLoaderGetConfigVars": "Isekai\\OIDC\\IsekaiOIDCAuthHooks::onResourceLoaderGetConfigVars"
|
||||||
|
},
|
||||||
|
"ExtensionMessagesFiles": {
|
||||||
|
"IsekaiOIDCAlias": "IsekaiOIDC.alias.php"
|
||||||
|
},
|
||||||
|
"SpecialPages": {
|
||||||
|
"IsekaiOIDCCallback": "Isekai\\OIDC\\SpecialIsekaiOIDCCallback"
|
||||||
|
},
|
||||||
|
"ResourceFileModulePaths": {
|
||||||
|
"localBasePath": "modules",
|
||||||
|
"remoteExtPath": "IsekaiOIDC/modules"
|
||||||
|
},
|
||||||
|
"AuthManagerAutoConfig": {
|
||||||
|
"primaryauth": {
|
||||||
|
"IsekaiOIDCAuth": {
|
||||||
|
"class": "Isekai\\OIDC\\IsekaiOIDCAuth",
|
||||||
|
"sort": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"APIModules": {
|
||||||
|
"oidcwebhook": "Isekai\\OIDC\\ApiOidcWebhook"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"IsekaiOIDC": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"manifest_version": 2
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"isekaioidc-loginbutton": "Isekai Account Login",
|
||||||
|
"isekaioidc-loginbutton-help": "Login with Isekai Project Account",
|
||||||
|
"isekaioidc-login-failed": "Unknow error while OIDC",
|
||||||
|
|
||||||
|
"apihelp-oidcwebhook-summary": "OpenID Connection Webhook",
|
||||||
|
"apihelp-oidcwebhook-param-provider": "Provider",
|
||||||
|
"apihelp-oidcwebhook-param-key": "Secret Key",
|
||||||
|
|
||||||
|
"isekaioidc-api-provider-not-supported": "Provider Unsupported",
|
||||||
|
"isekaioidc-api-key-invalid": "API Key invalid",
|
||||||
|
"isekaioidc-api-post-body-invalid": "POST Body format invalid",
|
||||||
|
"isekaioidc-api-user-not-exists": "User not exists",
|
||||||
|
"isekaioidc-api-cannot-refresh-token": "cannot update Access Token",
|
||||||
|
"isekaioidc-api-invalid-access-token": "Access Token format invalid",
|
||||||
|
"isekaioidc-api-cannot-request-api": "Cannot access to API",
|
||||||
|
"isekaioidc-api-unknow-apimode": "'apimode' in config unsupported",
|
||||||
|
|
||||||
|
"prefs-editavatar": "Avatar: ",
|
||||||
|
"prefs-global-profile": "Global profile: ",
|
||||||
|
"prefs-help-golbal-profile": "Change avatar, nickname on global account console.",
|
||||||
|
"btn-idp-profile-settings": "Account Console"
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"isekaioidc-loginbutton": "异世界百科账号登录",
|
||||||
|
"isekaioidc-loginbutton-help": "使用异世界百科账号登录",
|
||||||
|
"isekaioidc-login-failed": "无法登录,请求中发生错误",
|
||||||
|
|
||||||
|
"apihelp-oidcwebhook-summary": "OpenID Connection Webhook",
|
||||||
|
"apihelp-oidcwebhook-param-provider": "Provider",
|
||||||
|
"apihelp-oidcwebhook-param-key": "Secret Key",
|
||||||
|
|
||||||
|
"isekaioidc-api-provider-not-supported": "不支持的Provider",
|
||||||
|
"isekaioidc-api-key-invalid": "API Key错误",
|
||||||
|
"isekaioidc-api-post-body-invalid": "POST Body格式错误",
|
||||||
|
"isekaioidc-api-user-not-exists": "用户不存在",
|
||||||
|
"isekaioidc-api-cannot-refresh-token": "无法更新Access Token",
|
||||||
|
"isekaioidc-api-invalid-access-token": "Access Token格式不正确",
|
||||||
|
"isekaioidc-api-cannot-request-api": "无法请求API",
|
||||||
|
"isekaioidc-api-unknow-apimode": "配置文件中的apimode不支持",
|
||||||
|
|
||||||
|
"prefs-editavatar": "头像:",
|
||||||
|
"prefs-global-profile": "全局用户资料:",
|
||||||
|
"prefs-help-golbal-profile": "更改头像、昵称等信息",
|
||||||
|
"btn-idp-profile-settings": "前往用户中心"
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Isekai\OIDC;
|
||||||
|
|
||||||
|
use ApiBase;
|
||||||
|
use Wikimedia\ParamValidator\ParamValidator;
|
||||||
|
use Isekai\OIDC\IsekaiOIDCAuth;
|
||||||
|
use User;
|
||||||
|
|
||||||
|
class ApiOidcWebhook extends ApiBase {
|
||||||
|
public function __construct( $main, $method ) {
|
||||||
|
parent::__construct( $main->getMain(), $method );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() {
|
||||||
|
global $wgIsekaiOIDC;
|
||||||
|
$queryValues = $this->getRequest()->getQueryValues();
|
||||||
|
$provider = '';
|
||||||
|
if (isset($queryValues['provider'])) {
|
||||||
|
$provider = $queryValues['provider'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($wgIsekaiOIDC['webhookKey'])) {
|
||||||
|
if (!isset($queryValues['key']) || $queryValues['key'] !== $wgIsekaiOIDC['webhookKey']) {
|
||||||
|
$this->addError('isekaioidc-api-key-invalid', '-1', [ 'provider' => $provider ]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($provider) {
|
||||||
|
case 'keycloak':
|
||||||
|
$this->keycloakCallback();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->addError('isekaioidc-api-provider-not-supported', '-1', [ 'provider' => $provider ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function keycloakCallback() {
|
||||||
|
if (!$this->getRequest()->wasPosted()) {
|
||||||
|
return $this->addError('isekaioidc-api-post-body-invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
$postBody = $this->getRequest()->getRawPostString();
|
||||||
|
$postData = json_decode($postBody);
|
||||||
|
if (!$this->getRequest()->wasPosted() || !$postData) {
|
||||||
|
return $this->addError('isekaioidc-api-post-body-invalid');
|
||||||
|
}
|
||||||
|
global $wgIsekaiOIDC;
|
||||||
|
$realm = $wgIsekaiOIDC['realm'];
|
||||||
|
$apiMode = isset($wgIsekaiOIDC['apiMode']) ? $wgIsekaiOIDC['apiMode'] : 'oauth';
|
||||||
|
|
||||||
|
$eventType = $postData->type;
|
||||||
|
$subject = $postData->userId;
|
||||||
|
$eventRealm = $postData->realmId;
|
||||||
|
if ($eventRealm !== $realm) {
|
||||||
|
//return $this->addError('isekaioidc-api-realm-not-match');
|
||||||
|
$this->getResult()->addValue(null, 'webhook', 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($eventType, ['UPDATE_PROFILE', 'UPDATE_EMAIL'])) {
|
||||||
|
//return $this->addError('isekaioidc-api-unsupported-event');
|
||||||
|
$this->getResult()->addValue(null, 'webhook', 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($userId, $userName, $accessToken, $refreshToken) = IsekaiOIDCAuth::findUser($subject);
|
||||||
|
|
||||||
|
if ($userId) {
|
||||||
|
$userInfo = $postData->userInfo;
|
||||||
|
$newProfile = [
|
||||||
|
'realname' => $userInfo->name,
|
||||||
|
'email' => $userInfo->email,
|
||||||
|
'phone' => $userInfo->phone_number,
|
||||||
|
];
|
||||||
|
|
||||||
|
$user = User::newFromId($userId);
|
||||||
|
IsekaiOIDCAuth::updateUserInfo($user, $newProfile);
|
||||||
|
}
|
||||||
|
$this->getResult()->addValue(null, 'webhook', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllowedParams() {
|
||||||
|
return [
|
||||||
|
'provider' => [
|
||||||
|
ParamValidator::PARAM_DEFAULT => null,
|
||||||
|
ApiBase::PARAM_TYPE => 'text',
|
||||||
|
],
|
||||||
|
'key' => [
|
||||||
|
ParamValidator::PARAM_DEFAULT => null,
|
||||||
|
ApiBase::PARAM_TYPE => 'text',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,583 @@
|
|||||||
|
<?php
|
||||||
|
namespace Isekai\OIDC;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Jumbojett\OpenIDConnectClient;
|
||||||
|
use MediaWiki\Auth\AbstractPrimaryAuthenticationProvider;
|
||||||
|
use MediaWiki\Auth\AuthenticationRequest;
|
||||||
|
use MediaWiki\Auth\AuthenticationResponse;
|
||||||
|
use MediaWiki\Auth\AuthManager;
|
||||||
|
use MediaWiki\MediaWikiServices;
|
||||||
|
use MediaWiki\Session\SessionManager;
|
||||||
|
use StatusValue;
|
||||||
|
use User;
|
||||||
|
use Title;
|
||||||
|
use SpecialPage;
|
||||||
|
use Sanitizer;
|
||||||
|
use RequestContext;
|
||||||
|
use WebRequest;
|
||||||
|
|
||||||
|
class IsekaiOIDCAuth extends AbstractPrimaryAuthenticationProvider {
|
||||||
|
|
||||||
|
private $subject;
|
||||||
|
|
||||||
|
const LOG_TAG = 'Isekai OIDC';
|
||||||
|
|
||||||
|
const OIDC_TABLE = 'isekai_oidc';
|
||||||
|
|
||||||
|
const OIDC_FAKE_MAILDOMAIN = 'isekai.cn';
|
||||||
|
|
||||||
|
const OIDC_SUBJECT_SESSION_KEY = 'IsekaiOpenIDConnectSubject';
|
||||||
|
const USERNAME_SESSION_KEY = 'IsekaiOpenIDConnectUsername';
|
||||||
|
const REALNAME_SESSION_KEY = 'IsekaiOpenIDConnectRealname';
|
||||||
|
const EMAIL_SESSION_KEY = 'IsekaiOpenIDConnectEmail';
|
||||||
|
const PHONE_SESSION_KEY = 'IsekaiOpenIDConnectPhone';
|
||||||
|
//const BIO_SESSION_KEY = 'IsekaiOpenIDConnectBio';
|
||||||
|
const ACCESS_TOKEN_SESSION_KEY = 'IsekaiOpenIDConnectAccessToken';
|
||||||
|
const REFRESH_TOKEN_SESSION_KEY = 'IsekaiOpenIDConnectRefreshToken';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start an authentication flow
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function beginPrimaryAuthentication( array $reqs ) {
|
||||||
|
$oidc = self::getOpenIDConnectClient();
|
||||||
|
|
||||||
|
$request = RequestContext::getMain()->getRequest();
|
||||||
|
$returnTo = $request->getVal('returnto');
|
||||||
|
$returnToQuery = $request->getVal('returntoquery');
|
||||||
|
|
||||||
|
$session = SessionManager::getGlobalSession();
|
||||||
|
$authManager = MediaWikiServices::getInstance()->getAuthManager();
|
||||||
|
$authManager->setAuthenticationSessionData('AuthManagerSpecialPage:return:IsekaiOIDCCallback', [
|
||||||
|
'title' => 1,
|
||||||
|
'authAction' => 'IsekaiOIDCAuth',
|
||||||
|
'wpLoginToken' => $session->getToken('AuthManagerSpecialPage:IsekaiOIDCCallback'),
|
||||||
|
'wpRemember' => 1,
|
||||||
|
'returnto' => $returnTo,
|
||||||
|
'returntoquery' => $returnToQuery,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$url = $oidc->getAuthorizationUrl();
|
||||||
|
return AuthenticationResponse::newRedirect([
|
||||||
|
new IsekaiOIDCAuthBeginAuthenticationRequest(),
|
||||||
|
], $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue an authentication flow
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function continuePrimaryAuthentication( array $reqs ) {
|
||||||
|
global $wgIsekaiOIDC;
|
||||||
|
$config = $wgIsekaiOIDC;
|
||||||
|
|
||||||
|
$oidc =self::getOpenIDConnectClient();
|
||||||
|
if ($oidc->authenticate()) {
|
||||||
|
$accessToken = $oidc->getAccessToken();
|
||||||
|
$refreshToken = $oidc->getRefreshToken();
|
||||||
|
$payload = $oidc->getAccessTokenPayload();
|
||||||
|
$realname = $oidc->requestUserInfo( 'name' );
|
||||||
|
$email = $oidc->requestUserInfo( 'email' );
|
||||||
|
$phone = $oidc->requestUserInfo( 'phone_number' );
|
||||||
|
//$bio = $oidc->requestUserInfo( 'bio' );
|
||||||
|
$this->subject = $oidc->requestUserInfo( 'sub' );
|
||||||
|
// set rememberMe
|
||||||
|
if ( isset( $payload->remember_me ) && $payload->remember_me ) {
|
||||||
|
$GLOBALS['wgIsekaiOIDCRemember'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( method_exists( MediaWikiServices::class, 'getAuthManager' ) ) {
|
||||||
|
// MediaWiki 1.35+
|
||||||
|
$authManager = MediaWikiServices::getInstance()->getAuthManager();
|
||||||
|
} else {
|
||||||
|
$authManager = AuthManager::singleton();
|
||||||
|
}
|
||||||
|
$request = RequestContext::getMain()->getRequest();
|
||||||
|
$session = $request->getSession();
|
||||||
|
$session->clear('AuthManager::AutoCreateBlacklist'); // 防止缓存检测
|
||||||
|
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Real name: ' . $realname . ', Phone: ' . $phone .
|
||||||
|
', Email: ' . $email . ', Subject: ' . $this->subject . PHP_EOL );
|
||||||
|
|
||||||
|
$authManager->setAuthenticationSessionData(self::EMAIL_SESSION_KEY, $email);
|
||||||
|
$authManager->setAuthenticationSessionData(self::PHONE_SESSION_KEY, $phone);
|
||||||
|
$authManager->setAuthenticationSessionData(self::REALNAME_SESSION_KEY, $realname);
|
||||||
|
//$authManager->setAuthenticationSessionData(self::BIO_SESSION_KEY, $bio);
|
||||||
|
$authManager->setAuthenticationSessionData(self::ACCESS_TOKEN_SESSION_KEY, $accessToken);
|
||||||
|
$authManager->setAuthenticationSessionData(self::REFRESH_TOKEN_SESSION_KEY, $refreshToken);
|
||||||
|
|
||||||
|
list( $id, $username ) =
|
||||||
|
$this->findUser( $this->subject );
|
||||||
|
if ( $id !== null ) {
|
||||||
|
wfDebugLog( self::LOG_TAG,
|
||||||
|
'Found user with matching subject.' . PHP_EOL );
|
||||||
|
$authManager->setAuthenticationSessionData(self::USERNAME_SESSION_KEY, $username);
|
||||||
|
$this->updateUserInfo($username);
|
||||||
|
return AuthenticationResponse::newPass($username);
|
||||||
|
}
|
||||||
|
|
||||||
|
wfDebugLog( self::LOG_TAG,
|
||||||
|
'No user found with matching subject.' . PHP_EOL );
|
||||||
|
|
||||||
|
if ( isset($config['migrateBy']) && $config['migrateBy'] === 'email' ) {
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Checking for email migration.' .
|
||||||
|
PHP_EOL );
|
||||||
|
list( $id, $username ) = $this->getMigratedIdByEmail( $email );
|
||||||
|
if ( $id !== null ) {
|
||||||
|
$this->saveExtraAttributes( $id );
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Migrated user ' . $username .
|
||||||
|
' by email: ' . $email . '.' . PHP_EOL );
|
||||||
|
$authManager->setAuthenticationSessionData(self::USERNAME_SESSION_KEY, $username);
|
||||||
|
$this->updateUserInfo($username);
|
||||||
|
return AuthenticationResponse::newPass($username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$preferred_username = $this->getPreferredUsername( $config, $oidc,
|
||||||
|
$realname, $email );
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Preferred username: ' .
|
||||||
|
$preferred_username . PHP_EOL );
|
||||||
|
$authManager->setAuthenticationSessionData(self::USERNAME_SESSION_KEY, $preferred_username);
|
||||||
|
|
||||||
|
if ( !isset($config['migrateBy']) || $config['migrateBy'] === 'username' ) {
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Checking for username migration.' .
|
||||||
|
PHP_EOL );
|
||||||
|
$id = $this->getMigratedIdByUserName( $preferred_username );
|
||||||
|
if ( $id !== null ) {
|
||||||
|
$this->saveExtraAttributes( $id );
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Migrated user by username: ' .
|
||||||
|
$preferred_username . '.' . PHP_EOL );
|
||||||
|
$username = $preferred_username;
|
||||||
|
$authManager->setAuthenticationSessionData(self::USERNAME_SESSION_KEY, $username);
|
||||||
|
$this->updateUserInfo($username);
|
||||||
|
return AuthenticationResponse::newPass($username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = self::getAvailableUsername( $preferred_username,
|
||||||
|
$realname, $email );
|
||||||
|
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Available username: ' .
|
||||||
|
$username . PHP_EOL );
|
||||||
|
|
||||||
|
$authManager->setAuthenticationSessionData(
|
||||||
|
self::OIDC_SUBJECT_SESSION_KEY, $this->subject );
|
||||||
|
$authManager->setAuthenticationSessionData(self::USERNAME_SESSION_KEY, $username);
|
||||||
|
return AuthenticationResponse::newPass($username);
|
||||||
|
} else {
|
||||||
|
return AuthenticationResponse::newFail(wfMessage('isekaioidc-login-failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a property can change
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function providerAllowsPropertyChange( $property ) {
|
||||||
|
if (in_array($property, ['emailaddress', 'realname'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function autoCreatedAccount( $user, $source ) {
|
||||||
|
// user created
|
||||||
|
if ($user) {
|
||||||
|
$this->updateUserInfo($user);
|
||||||
|
$this->saveExtraAttributes($user->getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the named user exists
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function testUserExists( $username, $flags = User::READ_NORMAL ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a change of authentication data (e.g. passwords)
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function providerAllowsAuthenticationDataChange(
|
||||||
|
AuthenticationRequest $req, $checkData = true ) {
|
||||||
|
return StatusValue::newGood( 'ignored' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the account-creation type
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function accountCreationType() {
|
||||||
|
return self::TYPE_LINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start an account creation flow
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
|
||||||
|
return AuthenticationResponse::newAbstain();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change or remove authentication data (e.g. passwords)
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getOpenIDConnectClient() {
|
||||||
|
global $wgIsekaiOIDC;
|
||||||
|
if (!is_array($wgIsekaiOIDC)) {
|
||||||
|
wfDebugLog( self::LOG_TAG, 'wgIsekaiOIDC not set' .
|
||||||
|
PHP_EOL );
|
||||||
|
throw new Exception('wgIsekaiOIDC not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $wgIsekaiOIDC;
|
||||||
|
if (!isset($config['endpoint']) || !isset($config['clientID']) || !isset($config['clientSecret'])) {
|
||||||
|
wfDebugLog( self::LOG_TAG, 'wgIsekaiOIDC not valid' .
|
||||||
|
PHP_EOL );
|
||||||
|
throw new Exception('wgIsekaiOIDC not valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
$endpoint = $config['endpoint'];
|
||||||
|
$clientID = $config['clientID'];
|
||||||
|
$clientSecret = $config['clientSecret'];
|
||||||
|
$oidc = new OpenIDConnectClient($endpoint, $clientID, $clientSecret);
|
||||||
|
|
||||||
|
$redirectURL = $redirectURL = SpecialPage::getTitleFor('IsekaiOIDCCallback')->getFullURL();
|
||||||
|
$oidc->setRedirectURL( $redirectURL );
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Redirect URL: ' . $redirectURL );
|
||||||
|
|
||||||
|
if ( isset( $_REQUEST['forcelogin'] ) ) {
|
||||||
|
$oidc->addAuthParam( [ 'prompt' => 'login' ] );
|
||||||
|
}
|
||||||
|
if ( isset( $config['authparam'] ) &&
|
||||||
|
is_array( $config['authparam'] ) ) {
|
||||||
|
$oidc->addAuthParam( $config['authparam'] );
|
||||||
|
}
|
||||||
|
if ( isset( $config['scope'] ) ) {
|
||||||
|
$scope = $config['scope'];
|
||||||
|
if ( is_array( $scope ) ) {
|
||||||
|
foreach ( $scope as $s ) {
|
||||||
|
$oidc->addScope( $s );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$oidc->addScope( $scope );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( isset( $config['proxy'] ) ) {
|
||||||
|
$oidc->setHttpProxy( $config['proxy'] );
|
||||||
|
}
|
||||||
|
if ( isset( $config['verifyHost'] ) ) {
|
||||||
|
$oidc->setVerifyHost( $config['verifyHost'] );
|
||||||
|
}
|
||||||
|
if ( isset( $config['verifyPeer'] ) ) {
|
||||||
|
$oidc->setVerifyPeer( $config['verifyPeer'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $oidc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getAuthenticationRequests( $action, array $options ) {
|
||||||
|
switch ( $action ) {
|
||||||
|
case AuthManager::ACTION_LOGIN:
|
||||||
|
return [
|
||||||
|
new IsekaiOIDCAuthBeginAuthenticationRequest()
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户的个人信息
|
||||||
|
* @param User|string $user 用户
|
||||||
|
* @param array|null $data 用户信息
|
||||||
|
*/
|
||||||
|
public static function updateUserInfo($user, $data = null) {
|
||||||
|
if (is_string($user)) {
|
||||||
|
$user = User::newFromName($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data) {
|
||||||
|
$accessToken = isset($data['accessToken']) ? $data['accessToken'] : null;
|
||||||
|
$refreshToken = isset($data['refreshToken']) ? $data['refreshToken'] : null;
|
||||||
|
$newEmail = isset($data['email']) ? $data['email'] : null;
|
||||||
|
$newRealName = isset($data['realname']) ? $data['realname'] : null;
|
||||||
|
$newPhone = isset($data['phone']) ? $data['phone'] : null;
|
||||||
|
} else {
|
||||||
|
if ( method_exists( MediaWikiServices::class, 'getAuthManager' ) ) {
|
||||||
|
// MediaWiki 1.35+
|
||||||
|
$authManager = MediaWikiServices::getInstance()->getAuthManager();
|
||||||
|
} else {
|
||||||
|
$authManager = AuthManager::singleton();
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessToken = $authManager->getAuthenticationSessionData(self::ACCESS_TOKEN_SESSION_KEY);
|
||||||
|
$refreshToken = $authManager->getAuthenticationSessionData(self::REFRESH_TOKEN_SESSION_KEY);
|
||||||
|
$newEmail = $authManager->getAuthenticationSessionData(self::EMAIL_SESSION_KEY);
|
||||||
|
$newRealName = $authManager->getAuthenticationSessionData(self::REALNAME_SESSION_KEY);
|
||||||
|
$newPhone = $authManager->getAuthenticationSessionData(self::PHONE_SESSION_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$newEmail && $newPhone) { // 如果只设置了手机号,没有设置邮箱地址,则设置一个虚构的电子邮箱
|
||||||
|
$newEmail = self::punycodeEnc($user->getName()) . '@' . self::OIDC_FAKE_MAILDOMAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($accessToken) {
|
||||||
|
wfDebugLog( self::LOG_TAG,
|
||||||
|
'update access token for: ' . $user->getId() . '.' .
|
||||||
|
PHP_EOL );
|
||||||
|
$dbw = wfGetDB( DB_MASTER );
|
||||||
|
$dbw->upsert(
|
||||||
|
self::OIDC_TABLE,
|
||||||
|
[
|
||||||
|
'oidc_user' => $user->getId(),
|
||||||
|
'access_token' => $accessToken,
|
||||||
|
'refresh_token' => $refreshToken
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[ 'oidc_user' ]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'access_token' => $accessToken,
|
||||||
|
'refresh_token' => $refreshToken
|
||||||
|
],
|
||||||
|
__METHOD__
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$modified = false;
|
||||||
|
if ($newEmail && $newEmail != $user->mEmail && Sanitizer::validateEmail($newEmail)) {
|
||||||
|
$user->mEmail = $newEmail;
|
||||||
|
$user->confirmEmail();
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($newRealName && $newRealName != $user->mRealName) {
|
||||||
|
$user->mRealName = $newRealName;
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($modified) {
|
||||||
|
$user->saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.0
|
||||||
|
*
|
||||||
|
* @param int $id user id
|
||||||
|
*/
|
||||||
|
public function saveExtraAttributes( $id ) {
|
||||||
|
if ( method_exists( MediaWikiServices::class, 'getAuthManager' ) ) {
|
||||||
|
// MediaWiki 1.35+
|
||||||
|
$authManager = MediaWikiServices::getInstance()->getAuthManager();
|
||||||
|
} else {
|
||||||
|
$authManager = AuthManager::singleton();
|
||||||
|
}
|
||||||
|
if ( $this->subject === null ) {
|
||||||
|
$this->subject = $authManager->getAuthenticationSessionData(
|
||||||
|
self::OIDC_SUBJECT_SESSION_KEY );
|
||||||
|
$authManager->removeAuthenticationSessionData(
|
||||||
|
self::OIDC_SUBJECT_SESSION_KEY );
|
||||||
|
}
|
||||||
|
$dbw = wfGetDB( DB_MASTER );
|
||||||
|
$dbw->upsert(
|
||||||
|
self::OIDC_TABLE,
|
||||||
|
[
|
||||||
|
'oidc_user' => $id,
|
||||||
|
'oidc_subject' => $this->subject
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[ 'oidc_user' ]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'oidc_subject' => $this->subject
|
||||||
|
],
|
||||||
|
__METHOD__
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function findUser( $subject ) {
|
||||||
|
$dbr = wfGetDB( DB_REPLICA );
|
||||||
|
$row = $dbr->selectRow(
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
self::OIDC_TABLE
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id',
|
||||||
|
'user_name',
|
||||||
|
'access_token',
|
||||||
|
'refresh_token'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'oidc_subject' => $subject
|
||||||
|
],
|
||||||
|
__METHOD__,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
self::OIDC_TABLE => [ 'JOIN', [ 'user_id=oidc_user' ] ]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if ( $row === false ) {
|
||||||
|
return [ null, null, null, null ];
|
||||||
|
} else {
|
||||||
|
return [ $row->user_id, $row->user_name, $row->access_token, $row->refresh_token ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function findOidcDataByUserId( $userId ) {
|
||||||
|
$dbr = wfGetDB( DB_REPLICA );
|
||||||
|
$row = $dbr->selectRow(
|
||||||
|
[
|
||||||
|
self::OIDC_TABLE
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'oidc_user',
|
||||||
|
'oidc_subject',
|
||||||
|
'access_token',
|
||||||
|
'refresh_token'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'oidc_user' => $userId
|
||||||
|
],
|
||||||
|
__METHOD__
|
||||||
|
);
|
||||||
|
if ( $row === false ) {
|
||||||
|
return [ null, null, null ];
|
||||||
|
} else {
|
||||||
|
return [ $row->oidc_subject, $row->access_token, $row->refresh_token ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getPreferredUsername( $config, $oidc, $realname, $email ) {
|
||||||
|
if ( isset( $config['preferred_username'] ) ) {
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Using ' . $config['preferred_username'] .
|
||||||
|
' attribute for preferred username.' . PHP_EOL );
|
||||||
|
$preferred_username =
|
||||||
|
$oidc->requestUserInfo( $config['preferred_username'] );
|
||||||
|
} else {
|
||||||
|
$preferred_username = $oidc->requestUserInfo( 'preferred_username' );
|
||||||
|
}
|
||||||
|
if ( strlen( $preferred_username ) > 0 ) {
|
||||||
|
// do nothing
|
||||||
|
} elseif ( strlen( $realname ) > 0 && isset($config['migrateBy']) && $config['migrateBy'] === 'realname' ) {
|
||||||
|
$preferred_username = $realname;
|
||||||
|
} elseif ( strlen( $email ) > 0 && isset($config['migrateBy']) && $config['migrateBy'] === 'email' ) {
|
||||||
|
$pos = strpos( $email, '@' );
|
||||||
|
if ( $pos !== false && $pos > 0 ) {
|
||||||
|
$preferred_username = substr( $email, 0, $pos );
|
||||||
|
} else {
|
||||||
|
$preferred_username = $email;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$nt = Title::makeTitleSafe( NS_USER, $preferred_username );
|
||||||
|
if ( $nt === null ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $nt->getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getMigratedIdByUserName( $username ) {
|
||||||
|
$nt = Title::makeTitleSafe( NS_USER, $username );
|
||||||
|
if ( $nt === null ) {
|
||||||
|
wfDebugLog( self::LOG_TAG,
|
||||||
|
'Invalid preferred username for migration: ' . $username . '.' .
|
||||||
|
PHP_EOL );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$username = $nt->getText();
|
||||||
|
$dbr = wfGetDB( DB_REPLICA );
|
||||||
|
$row = $dbr->selectRow(
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
self::OIDC_TABLE
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_name' => $username,
|
||||||
|
'oidc_user' => null
|
||||||
|
],
|
||||||
|
__METHOD__,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
self::OIDC_TABLE => [ 'LEFT JOIN', [ 'user_id=oidc_user' ] ]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if ( $row !== false ) {
|
||||||
|
return $row->user_id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getMigratedIdByEmail( $email ) {
|
||||||
|
wfDebugLog( self::LOG_TAG, 'Matching user to email ' . $email . '.' .
|
||||||
|
PHP_EOL );
|
||||||
|
$dbr = wfGetDB( DB_REPLICA );
|
||||||
|
$row = $dbr->selectRow(
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
self::OIDC_TABLE
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id',
|
||||||
|
'user_name',
|
||||||
|
'oidc_user'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_email' => $email
|
||||||
|
],
|
||||||
|
__METHOD__,
|
||||||
|
[
|
||||||
|
// if multiple matching accounts, use the oldest one
|
||||||
|
'ORDER BY' => 'user_registration'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
self::OIDC_TABLE => [ 'LEFT JOIN', [ 'user_id=oidc_user' ] ]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if ( $row !== false && $row->oidc_user === null ) {
|
||||||
|
return [ $row->user_id, $row->user_name ];
|
||||||
|
}
|
||||||
|
return [ null, null ];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getAvailableUsername( $preferred_username ) {
|
||||||
|
if ( $preferred_username === null ) {
|
||||||
|
$preferred_username = 'User';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( User::idFromName( $preferred_username ) === null ) {
|
||||||
|
return $preferred_username;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = 1;
|
||||||
|
while ( User::idFromName( $preferred_username . $count ) !== null ) {
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
return $preferred_username . $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function punycodeEnc($str){
|
||||||
|
$punycode = new PunyCode();
|
||||||
|
return $punycode->encode($str);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
namespace Isekai\OIDC;
|
||||||
|
|
||||||
|
use MediaWiki\Auth\AuthManager;
|
||||||
|
use MediaWiki\Auth\ButtonAuthenticationRequest;
|
||||||
|
|
||||||
|
class IsekaiOIDCAuthBeginAuthenticationRequest extends ButtonAuthenticationRequest {
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(
|
||||||
|
'isekaioidclogin',
|
||||||
|
wfMessage( 'isekaioidc-loginbutton' ),
|
||||||
|
wfMessage( 'isekaioidc-loginbutton-help' ),
|
||||||
|
true );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns field information.
|
||||||
|
* @return array field information
|
||||||
|
*/
|
||||||
|
public function getFieldInfo() {
|
||||||
|
if ( $this->action !== AuthManager::ACTION_LOGIN ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return parent::getFieldInfo();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
namespace Isekai\OIDC;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Html;
|
||||||
|
use MediaWiki\MediaWikiServices;
|
||||||
|
use User;
|
||||||
|
use OutputPage;
|
||||||
|
use RequestContext;
|
||||||
|
use MediaWiki\Session\SessionManager;
|
||||||
|
use Title;
|
||||||
|
|
||||||
|
class IsekaiOIDCAuthHooks {
|
||||||
|
const SYNLOGIN_SESSIONKEY = 'IsekaiOIDCSyncLogin';
|
||||||
|
const SYNLOGOUT_SESSIONKEY = 'IsekaiOIDCSyncLogout';
|
||||||
|
|
||||||
|
public static function onRegistration() {
|
||||||
|
$passwordProviders = [
|
||||||
|
'MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider',
|
||||||
|
'MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider'
|
||||||
|
];
|
||||||
|
$providers = $GLOBALS['wgAuthManagerAutoConfig'];
|
||||||
|
if ( isset( $providers['primaryauth'] ) ) {
|
||||||
|
$primaries = $providers['primaryauth'];
|
||||||
|
foreach ( $primaries as $key => $provider ) {
|
||||||
|
if ( in_array( $provider['class'], $passwordProviders ) ) {
|
||||||
|
unset( $GLOBALS['wgAuthManagerAutoConfig']['primaryauth'][$key] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements LoadExtensionSchemaUpdates hook.
|
||||||
|
*
|
||||||
|
* @param DatabaseUpdater $updater
|
||||||
|
*/
|
||||||
|
public static function loadExtensionSchemaUpdates( $updater ) {
|
||||||
|
$dir = dirname(__DIR__) . '/sql/';
|
||||||
|
$type = $updater->getDB()->getType();
|
||||||
|
$updater->addExtensionTable( 'isekai_oidc',
|
||||||
|
$dir . $type . '/AddTable.sql' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements PageBeforeDisplay hook.
|
||||||
|
* @param OutputPage $out
|
||||||
|
*/
|
||||||
|
public static function onBeforePageDisplay($out) {
|
||||||
|
global $wgIsekaiOIDC;
|
||||||
|
|
||||||
|
if (isset($wgIsekaiOIDC['syncLogout']) && $wgIsekaiOIDC['syncLogout']) {
|
||||||
|
$title = $out->getTitle();
|
||||||
|
$request = RequestContext::getMain()->getRequest();
|
||||||
|
$needLogout = $request->getCookie(self::SYNLOGOUT_SESSIONKEY);
|
||||||
|
$isLogoutPage = $title != null && $title->isSpecial('Userlogout');
|
||||||
|
if ($isLogoutPage || $needLogout) { // 需要重定向到用户退出页面
|
||||||
|
$old_name = $request->getCookie('UserName');
|
||||||
|
wfDebugLog('IsekaiOIDC', 'oldUser: ' . $old_name);
|
||||||
|
|
||||||
|
$oldUser = User::newFromName($old_name);
|
||||||
|
if ( $oldUser === false ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list($subject, $accessToken, $refreshToken) = IsekaiOIDCAuth::findOidcDataByUserId($oldUser->getId());
|
||||||
|
if ($subject != null) {
|
||||||
|
$redirectUri = '';
|
||||||
|
if ($isLogoutPage) {
|
||||||
|
$returnto = $request->getVal('returnto');
|
||||||
|
$returntoquery = $request->getVal('returntoquery');
|
||||||
|
if ($returnto) {
|
||||||
|
$returnto = Title::newFromText($returnto);
|
||||||
|
} else {
|
||||||
|
$returnto = Title::newMainPage();
|
||||||
|
}
|
||||||
|
if (!$returnto) {
|
||||||
|
$returnto = Title::newMainPage();
|
||||||
|
}
|
||||||
|
$redirectUri = Title::newMainPage()->getFullURL($returntoquery);
|
||||||
|
} else if ($title != null) {
|
||||||
|
$redirectUri = $title->getFullURL();
|
||||||
|
} else {
|
||||||
|
$redirectUri = Title::newMainPage()->getFullURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = $wgIsekaiOIDC['endpoint'] . 'protocol/openid-connect/logout?' .
|
||||||
|
http_build_query([ 'redirect_uri' => $redirectUri ]);
|
||||||
|
wfDebugLog('IsekaiOIDC', 'logout url: ' . $url);
|
||||||
|
$response = $request->response();
|
||||||
|
$response->clearCookie(self::SYNLOGOUT_SESSIONKEY);
|
||||||
|
$response->header('Location: ' . $url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function onLogout($user, &$injected_html) {
|
||||||
|
$request = RequestContext::getMain()->getRequest();
|
||||||
|
$request->response()->setCookie(self::SYNLOGOUT_SESSIONKEY, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function onGetPreferences(\User $user, &$preferences) {
|
||||||
|
global $wgIsekaiOIDC;
|
||||||
|
$profileUrl = $wgIsekaiOIDC['endpoint'] . 'account/';
|
||||||
|
$referrer = $wgIsekaiOIDC['clientID'];
|
||||||
|
$referrerUri = Title::newFromText('Special:Preferences')->getCanonicalURL();
|
||||||
|
$profileUrl .= '?' . http_build_query([
|
||||||
|
'referrer' => $referrer,
|
||||||
|
'referrer_uri' => $referrerUri
|
||||||
|
]);
|
||||||
|
|
||||||
|
$preferences['global-profile'] = array(
|
||||||
|
'type' => 'info',
|
||||||
|
'raw' => true,
|
||||||
|
'label-message' => 'prefs-global-profile',
|
||||||
|
'help-message' => 'prefs-help-golbal-profile',
|
||||||
|
'default' => strval(new \OOUI\ButtonWidget([
|
||||||
|
'id' => 'global-profile-link',
|
||||||
|
'href' => $profileUrl,
|
||||||
|
'label' => wfMessage('btn-idp-profile-settings')->text(),
|
||||||
|
])),
|
||||||
|
'section' => 'personal/info',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($wgIsekaiOIDC['avatarUrl'])) {
|
||||||
|
list($subject, $accessToken, $refreshToken) = IsekaiOIDCAuth::findOidcDataByUserId($user->getId());
|
||||||
|
$avatarLink = str_replace(['{openid}', '{username}'], [urlencode($subject), urlencode($user->getName())], $wgIsekaiOIDC['avatarUrl']);
|
||||||
|
$preferences['editavatar'] = array(
|
||||||
|
'type' => 'info',
|
||||||
|
'raw' => true,
|
||||||
|
'label-message' => 'prefs-editavatar',
|
||||||
|
'default' => Html::openElement('a', [
|
||||||
|
'href' => $profileUrl,
|
||||||
|
]) .
|
||||||
|
'<img src="' . $avatarLink . '" width="64" height="64"></img>' .
|
||||||
|
Html::closeElement('a'),
|
||||||
|
'section' => 'personal/info',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array &$personal_urls
|
||||||
|
* @param Title $title
|
||||||
|
* @param \SkinTemplate $skin
|
||||||
|
*/
|
||||||
|
public static function onPersonalUrls(&$personal_urls, $title, $skin) {
|
||||||
|
if (isset($personal_urls['createaccount'])) {
|
||||||
|
unset($personal_urls['createaccount']);
|
||||||
|
}
|
||||||
|
if (isset($personal_urls['login'])) {
|
||||||
|
$personal_urls['login']['text'] = wfMessage('nav-login-createaccount')->text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array &$personal_urls
|
||||||
|
* @param string $skin
|
||||||
|
* @param \Config $config
|
||||||
|
*/
|
||||||
|
public static function onResourceLoaderGetConfigVars(&$vars, $skin, $config) {
|
||||||
|
global $wgIsekaiOIDC;
|
||||||
|
if (isset($wgIsekaiOIDC['avatarUrl'])) {
|
||||||
|
$vars['wgAvatarTemplate'] = $wgIsekaiOIDC['avatarUrl'];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,355 @@
|
|||||||
|
<?php
|
||||||
|
namespace Isekai\OIDC;
|
||||||
|
/**
|
||||||
|
* Punycode implementation as described in RFC 3492
|
||||||
|
*
|
||||||
|
* @link http://tools.ietf.org/html/rfc3492
|
||||||
|
*/
|
||||||
|
class PunyCode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstring parameter values
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const BASE = 36;
|
||||||
|
const TMIN = 1;
|
||||||
|
const TMAX = 26;
|
||||||
|
const SKEW = 38;
|
||||||
|
const DAMP = 700;
|
||||||
|
const INITIAL_BIAS = 72;
|
||||||
|
const INITIAL_N = 128;
|
||||||
|
const PREFIX = 'xn--';
|
||||||
|
const DELIMITER = '-';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode table
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
*/
|
||||||
|
protected static $encodeTable = array(
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
|
||||||
|
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode table
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
*/
|
||||||
|
protected static $decodeTable = array(
|
||||||
|
'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5,
|
||||||
|
'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11,
|
||||||
|
'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
|
||||||
|
's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
|
||||||
|
'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
|
||||||
|
'4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Character encoding
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
protected $encoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $encoding Character encoding
|
||||||
|
*/
|
||||||
|
public function __construct($encoding = 'UTF-8')
|
||||||
|
{
|
||||||
|
$this->encoding = $encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a domain to its Punycode version
|
||||||
|
*
|
||||||
|
* @param string $input Domain name in Unicode to be encoded
|
||||||
|
* @return string Punycode representation in ASCII
|
||||||
|
*/
|
||||||
|
public function encode($input)
|
||||||
|
{
|
||||||
|
$input = mb_strtolower($input, $this->encoding);
|
||||||
|
$parts = explode('.', $input);
|
||||||
|
foreach ($parts as &$part) {
|
||||||
|
$length = strlen($part);
|
||||||
|
if ($length < 1) {
|
||||||
|
throw new \Exception(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
|
||||||
|
}
|
||||||
|
$part = $this->encodePart($part);
|
||||||
|
}
|
||||||
|
$output = implode('.', $parts);
|
||||||
|
$length = strlen($output);
|
||||||
|
if ($length > 255) {
|
||||||
|
throw new \Exception(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a part of a domain name, such as tld, to its Punycode version
|
||||||
|
*
|
||||||
|
* @param string $input Part of a domain name
|
||||||
|
* @return string Punycode representation of a domain part
|
||||||
|
*/
|
||||||
|
protected function encodePart($input)
|
||||||
|
{
|
||||||
|
$codePoints = $this->listCodePoints($input);
|
||||||
|
|
||||||
|
$n = static::INITIAL_N;
|
||||||
|
$bias = static::INITIAL_BIAS;
|
||||||
|
$delta = 0;
|
||||||
|
$h = $b = count($codePoints['basic']);
|
||||||
|
|
||||||
|
$output = '';
|
||||||
|
foreach ($codePoints['basic'] as $code) {
|
||||||
|
$output .= $this->codePointToChar($code);
|
||||||
|
}
|
||||||
|
if ($input === $output) {
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
if ($b > 0) {
|
||||||
|
$output .= static::DELIMITER;
|
||||||
|
}
|
||||||
|
|
||||||
|
$codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
|
||||||
|
sort($codePoints['nonBasic']);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$length = mb_strlen($input, $this->encoding);
|
||||||
|
while ($h < $length) {
|
||||||
|
$m = $codePoints['nonBasic'][$i++];
|
||||||
|
$delta = $delta + ($m - $n) * ($h + 1);
|
||||||
|
$n = $m;
|
||||||
|
|
||||||
|
foreach ($codePoints['all'] as $c) {
|
||||||
|
if ($c < $n || $c < static::INITIAL_N) {
|
||||||
|
$delta++;
|
||||||
|
}
|
||||||
|
if ($c === $n) {
|
||||||
|
$q = $delta;
|
||||||
|
for ($k = static::BASE;; $k += static::BASE) {
|
||||||
|
$t = $this->calculateThreshold($k, $bias);
|
||||||
|
if ($q < $t) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = $t + (($q - $t) % (static::BASE - $t));
|
||||||
|
$output .= static::$encodeTable[$code];
|
||||||
|
|
||||||
|
$q = ($q - $t) / (static::BASE - $t);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output .= static::$encodeTable[$q];
|
||||||
|
$bias = $this->adapt($delta, $h + 1, ($h === $b));
|
||||||
|
$delta = 0;
|
||||||
|
$h++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$delta++;
|
||||||
|
$n++;
|
||||||
|
}
|
||||||
|
$out = static::PREFIX . $output;
|
||||||
|
$length = strlen($out);
|
||||||
|
if ($length > 63 || $length < 1) {
|
||||||
|
throw new \Exception(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a Punycode domain name to its Unicode counterpart
|
||||||
|
*
|
||||||
|
* @param string $input Domain name in Punycode
|
||||||
|
* @return string Unicode domain name
|
||||||
|
*/
|
||||||
|
public function decode($input)
|
||||||
|
{
|
||||||
|
$input = strtolower($input);
|
||||||
|
$parts = explode('.', $input);
|
||||||
|
foreach ($parts as &$part) {
|
||||||
|
$length = strlen($part);
|
||||||
|
if ($length > 63 || $length < 1) {
|
||||||
|
throw new \Exception(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
|
||||||
|
}
|
||||||
|
if (strpos($part, static::PREFIX) !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$part = substr($part, strlen(static::PREFIX));
|
||||||
|
$part = $this->decodePart($part);
|
||||||
|
}
|
||||||
|
$output = implode('.', $parts);
|
||||||
|
$length = strlen($output);
|
||||||
|
if ($length > 255) {
|
||||||
|
throw new \Exception(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a part of domain name, such as tld
|
||||||
|
*
|
||||||
|
* @param string $input Part of a domain name
|
||||||
|
* @return string Unicode domain part
|
||||||
|
*/
|
||||||
|
protected function decodePart($input)
|
||||||
|
{
|
||||||
|
$n = static::INITIAL_N;
|
||||||
|
$i = 0;
|
||||||
|
$bias = static::INITIAL_BIAS;
|
||||||
|
$output = '';
|
||||||
|
|
||||||
|
$pos = strrpos($input, static::DELIMITER);
|
||||||
|
if ($pos !== false) {
|
||||||
|
$output = substr($input, 0, $pos++);
|
||||||
|
} else {
|
||||||
|
$pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputLength = strlen($output);
|
||||||
|
$inputLength = strlen($input);
|
||||||
|
while ($pos < $inputLength) {
|
||||||
|
$oldi = $i;
|
||||||
|
$w = 1;
|
||||||
|
|
||||||
|
for ($k = static::BASE;; $k += static::BASE) {
|
||||||
|
$digit = static::$decodeTable[$input[$pos++]];
|
||||||
|
$i = $i + ($digit * $w);
|
||||||
|
$t = $this->calculateThreshold($k, $bias);
|
||||||
|
|
||||||
|
if ($digit < $t) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$w = $w * (static::BASE - $t);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bias = $this->adapt($i - $oldi, ++$outputLength, ($oldi === 0));
|
||||||
|
$n = $n + (int) ($i / $outputLength);
|
||||||
|
$i = $i % ($outputLength);
|
||||||
|
$output = mb_substr($output, 0, $i, $this->encoding) . $this->codePointToChar($n) . mb_substr($output, $i, $outputLength - 1, $this->encoding);
|
||||||
|
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the bias threshold to fall between TMIN and TMAX
|
||||||
|
*
|
||||||
|
* @param integer $k
|
||||||
|
* @param integer $bias
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
protected function calculateThreshold($k, $bias)
|
||||||
|
{
|
||||||
|
if ($k <= $bias + static::TMIN) {
|
||||||
|
return static::TMIN;
|
||||||
|
} elseif ($k >= $bias + static::TMAX) {
|
||||||
|
return static::TMAX;
|
||||||
|
}
|
||||||
|
return $k - $bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bias adaptation
|
||||||
|
*
|
||||||
|
* @param integer $delta
|
||||||
|
* @param integer $numPoints
|
||||||
|
* @param boolean $firstTime
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
protected function adapt($delta, $numPoints, $firstTime)
|
||||||
|
{
|
||||||
|
$delta = (int) (
|
||||||
|
($firstTime)
|
||||||
|
? $delta / static::DAMP
|
||||||
|
: $delta / 2
|
||||||
|
);
|
||||||
|
$delta += (int) ($delta / $numPoints);
|
||||||
|
|
||||||
|
$k = 0;
|
||||||
|
while ($delta > ((static::BASE - static::TMIN) * static::TMAX) / 2) {
|
||||||
|
$delta = (int) ($delta / (static::BASE - static::TMIN));
|
||||||
|
$k = $k + static::BASE;
|
||||||
|
}
|
||||||
|
$k = $k + (int) (((static::BASE - static::TMIN + 1) * $delta) / ($delta + static::SKEW));
|
||||||
|
|
||||||
|
return $k;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List code points for a given input
|
||||||
|
*
|
||||||
|
* @param string $input
|
||||||
|
* @return array Multi-dimension array with basic, non-basic and aggregated code points
|
||||||
|
*/
|
||||||
|
protected function listCodePoints($input)
|
||||||
|
{
|
||||||
|
$codePoints = array(
|
||||||
|
'all' => array(),
|
||||||
|
'basic' => array(),
|
||||||
|
'nonBasic' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$length = mb_strlen($input, $this->encoding);
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$char = mb_substr($input, $i, 1, $this->encoding);
|
||||||
|
$code = $this->charToCodePoint($char);
|
||||||
|
if ($code < 128) {
|
||||||
|
$codePoints['all'][] = $codePoints['basic'][] = $code;
|
||||||
|
} else {
|
||||||
|
$codePoints['all'][] = $codePoints['nonBasic'][] = $code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $codePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a single or multi-byte character to its code point
|
||||||
|
*
|
||||||
|
* @param string $char
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
protected function charToCodePoint($char)
|
||||||
|
{
|
||||||
|
$code = ord($char[0]);
|
||||||
|
if ($code < 128) {
|
||||||
|
return $code;
|
||||||
|
} elseif ($code < 224) {
|
||||||
|
return (($code - 192) * 64) + (ord($char[1]) - 128);
|
||||||
|
} elseif ($code < 240) {
|
||||||
|
return (($code - 224) * 4096) + ((ord($char[1]) - 128) * 64) + (ord($char[2]) - 128);
|
||||||
|
} else {
|
||||||
|
return (($code - 240) * 262144) + ((ord($char[1]) - 128) * 4096) + ((ord($char[2]) - 128) * 64) + (ord($char[3]) - 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a code point to its single or multi-byte character
|
||||||
|
*
|
||||||
|
* @param integer $code
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function codePointToChar($code)
|
||||||
|
{
|
||||||
|
if ($code <= 0x7F) {
|
||||||
|
return chr($code);
|
||||||
|
} elseif ($code <= 0x7FF) {
|
||||||
|
return chr(($code >> 6) + 192) . chr(($code & 63) + 128);
|
||||||
|
} elseif ($code <= 0xFFFF) {
|
||||||
|
return chr(($code >> 12) + 224) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
|
||||||
|
} else {
|
||||||
|
return chr(($code >> 18) + 240) . chr((($code >> 12) & 63) + 128) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
namespace Isekai\OIDC;
|
||||||
|
|
||||||
|
use MediaWiki\Auth\AuthManager;
|
||||||
|
use MediaWiki\Logger\LoggerFactory;
|
||||||
|
use LoginSignupSpecialPage;
|
||||||
|
use LoginHelper;
|
||||||
|
|
||||||
|
class SpecialIsekaiOIDCCallback extends LoginSignupSpecialPage {
|
||||||
|
protected static $allowedActions = [
|
||||||
|
AuthManager::ACTION_LOGIN,
|
||||||
|
AuthManager::ACTION_LOGIN_CONTINUE
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static $messages = [
|
||||||
|
'authform-newtoken' => 'nocookiesforlogin',
|
||||||
|
'authform-notoken' => 'sessionfailure',
|
||||||
|
'authform-wrongtoken' => 'sessionfailure',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct( 'IsekaiOIDCCallback' );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doesWrites() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLoginSecurityLevel() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDefaultAction( $subPage ) {
|
||||||
|
return AuthManager::ACTION_LOGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription() {
|
||||||
|
return $this->msg( 'login' )->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHeaders() {
|
||||||
|
// override the page title if we are doing a forced reauthentication
|
||||||
|
parent::setHeaders();
|
||||||
|
if ( $this->securityLevel && $this->getUser()->isLoggedIn() ) {
|
||||||
|
$this->getOutput()->setPageTitle( $this->msg( 'login-security' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isSignup() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run any hooks registered for logins, then HTTP redirect to
|
||||||
|
* $this->mReturnTo (or Main Page if that's undefined). Formerly we had a
|
||||||
|
* nice message here, but that's really not as useful as just being sent to
|
||||||
|
* wherever you logged in from. It should be clear that the action was
|
||||||
|
* successful, given the lack of error messages plus the appearance of your
|
||||||
|
* name in the upper right.
|
||||||
|
* @param bool $direct True if the action was successful just now; false if that happened
|
||||||
|
* pre-redirection (so this handler was called already)
|
||||||
|
* @param StatusValue|null $extraMessages
|
||||||
|
*/
|
||||||
|
protected function successfulAction( $direct = false, $extraMessages = null ) {
|
||||||
|
global $wgSecureLogin, $wgIsekaiOIDCRemember;
|
||||||
|
|
||||||
|
$user = $this->targetUser ?: $this->getUser();
|
||||||
|
$session = $this->getRequest()->getSession();
|
||||||
|
|
||||||
|
if ( $wgIsekaiOIDCRemember ) {
|
||||||
|
$session->setRememberUser(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $direct ) {
|
||||||
|
$user->touch();
|
||||||
|
|
||||||
|
$this->clearToken();
|
||||||
|
|
||||||
|
if ( $user->requiresHTTPS() ) {
|
||||||
|
$this->mStickHTTPS = true;
|
||||||
|
}
|
||||||
|
$session->setForceHTTPS( $wgSecureLogin && $this->mStickHTTPS );
|
||||||
|
|
||||||
|
// If the user does not have a session cookie at this point, they probably need to
|
||||||
|
// do something to their browser.
|
||||||
|
if ( !$this->hasSessionCookie() ) {
|
||||||
|
$this->mainLoginForm( [ /*?*/ ], $session->getProvider()->whyNoSession() );
|
||||||
|
// TODO something more specific? This used to use nocookieslogin
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run any hooks; display injected HTML if any, else redirect
|
||||||
|
$injected_html = '';
|
||||||
|
$this->getHookRunner()->onUserLoginComplete(
|
||||||
|
$user, $injected_html, $direct );
|
||||||
|
|
||||||
|
if ( $injected_html !== '' || $extraMessages ) {
|
||||||
|
$this->showSuccessPage( 'success', $this->msg( 'loginsuccesstitle' ),
|
||||||
|
'loginsuccess', $injected_html, $extraMessages );
|
||||||
|
} else {
|
||||||
|
$helper = new LoginHelper( $this->getContext() );
|
||||||
|
$helper->showReturnToPage( 'successredirect', $this->mReturnTo, $this->mReturnToQuery,
|
||||||
|
$this->mStickHTTPS );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getToken() {
|
||||||
|
return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:IsekaiOIDCCallback' );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function clearToken() {
|
||||||
|
return $this->getRequest()->getSession()->resetToken( 'AuthManagerSpecialPage:IsekaiOIDCCallback' );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTokenName() {
|
||||||
|
return 'wpLoginToken';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getGroupName() {
|
||||||
|
return 'login';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function logAuthResult( $success, $status = null ) {
|
||||||
|
LoggerFactory::getInstance( 'authevents' )->info( 'Login attempt', [
|
||||||
|
'event' => 'login',
|
||||||
|
'successful' => $success,
|
||||||
|
'status' => $status,
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE /*_*/isekai_oidc (
|
||||||
|
oidc_user INT UNSIGNED PRIMARY KEY NOT NULL,
|
||||||
|
oidc_subject VARCHAR(255) NOT NULL,
|
||||||
|
access_token TEXT,
|
||||||
|
refresh_token TEXT
|
||||||
|
) /*$wgDBTableOptions*/;
|
||||||
|
CREATE INDEX /*i*/isekai_oidc_subject ON /*_*/isekai_oidc(oidc_subject);
|
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE isekai_oidc (
|
||||||
|
oidc_user INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
oidc_subject TEXT NOT NULL,
|
||||||
|
access_token TEXT,
|
||||||
|
refresh_token TEXT
|
||||||
|
);
|
||||||
|
CREATE INDEX isekai_oidc_subject ON isekai_oidc oidc_subject;
|
Loading…
Reference in New Issue