You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
602 lines
18 KiB
PHP
602 lines
18 KiB
PHP
<?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 MediaWiki\User\User;
|
|
use MediaWiki\Title\Title;
|
|
use MediaWiki\SpecialPage\SpecialPage;
|
|
use MediaWiki\Parser\Sanitizer;
|
|
use MediaWiki\Context\RequestContext;
|
|
use MediaWiki\Message\Message;
|
|
use StatusValue;
|
|
|
|
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 ) {
|
|
$services = MediaWikiServices::getInstance();
|
|
$config = $services->getMainConfig()->get('IsekaiOIDC');
|
|
|
|
$oidc = self::getOpenIDConnectClient();
|
|
|
|
$requestCtx = RequestContext::getMain();
|
|
|
|
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;
|
|
}
|
|
|
|
$authManager = MediaWikiServices::getInstance()->getAuthManager();
|
|
$request = $requestCtx->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 {
|
|
$message = Message::newFromSpecifier('isekaioidc-login-failed');
|
|
return AuthenticationResponse::newFail($message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
$services = MediaWikiServices::getInstance();
|
|
$config = $services->getMainConfig()->get('IsekaiOIDC');
|
|
|
|
if (!is_array($config)) {
|
|
wfDebugLog( self::LOG_TAG, 'wgIsekaiOIDC not set' .
|
|
PHP_EOL );
|
|
throw new Exception('wgIsekaiOIDC not set');
|
|
}
|
|
|
|
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['force'] ) || 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 ) ) {
|
|
$scope = [ $scope ];
|
|
}
|
|
|
|
$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 = MediaWikiServices::getInstance()->getUserFactory()->newFromName($user);
|
|
}
|
|
|
|
if ($data) {
|
|
$accessToken = $data['accessToken'] ?? null;
|
|
$refreshToken = $data['refreshToken'] ?? null;
|
|
$newEmail = $data['email'] ?? null;
|
|
$newRealName = $data['realname'] ?? null;
|
|
$newPhone = $data['phone'] ?? null;
|
|
} else {
|
|
$authManager = MediaWikiServices::getInstance()->getAuthManager();
|
|
|
|
$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 = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
|
|
$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 ) {
|
|
$authManager = MediaWikiServices::getInstance()->getAuthManager();
|
|
|
|
if ( $this->subject === null ) {
|
|
$this->subject = $authManager->getAuthenticationSessionData(
|
|
self::OIDC_SUBJECT_SESSION_KEY );
|
|
$authManager->removeAuthenticationSessionData(
|
|
self::OIDC_SUBJECT_SESSION_KEY );
|
|
}
|
|
$dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
|
|
$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 = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( 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 = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( 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 ];
|
|
}
|
|
}
|
|
|
|
public static function findOidcSubjectsByUserIds( array $userIds ) {
|
|
$dbr = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_REPLICA );
|
|
$rows = $dbr->select(
|
|
[
|
|
self::OIDC_TABLE
|
|
],
|
|
[
|
|
'oidc_user',
|
|
'oidc_subject'
|
|
],
|
|
[
|
|
'oidc_user' => $userIds
|
|
],
|
|
__METHOD__
|
|
);
|
|
$subjects = [];
|
|
foreach ( $rows as $row ) {
|
|
$subjects[$row->oidc_user] = $row->oidc_subject;
|
|
}
|
|
return $subjects;
|
|
}
|
|
|
|
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 = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( 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 = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( 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';
|
|
}
|
|
|
|
$userIdentityLookup = MediaWikiServices::getInstance()->getUserIdentityLookup();
|
|
$userIdentity = $userIdentityLookup->getUserIdentityByName( $preferred_username );
|
|
if ( !$userIdentity || !$userIdentity->isRegistered() ) {
|
|
return $preferred_username;
|
|
}
|
|
|
|
$count = 1;
|
|
while ( true ) {
|
|
$userIdentity = $userIdentityLookup->getUserIdentityByName( $preferred_username . $count );
|
|
if ( !$userIdentity || !$userIdentity->isRegistered() ) {
|
|
break;
|
|
}
|
|
$count ++;
|
|
}
|
|
return $preferred_username . $count;
|
|
}
|
|
|
|
protected static function punycodeEnc( $str ){
|
|
$punycode = new PunyCode();
|
|
return $punycode->encode( $str );
|
|
}
|
|
}
|