From 6e68f8fe4f96918b6f69d0993a6b21d43e835c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Bompard?= Date: Thu, 25 Mar 2021 11:15:21 +0100 Subject: [PATCH] Fix the mediawiki auth plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aurélien Bompard --- roles/mediawiki/files/OpenIDConnect.class.php | 376 ------------- roles/mediawiki/files/OpenIDConnect.php | 492 ++++++++++++++++++ roles/mediawiki/tasks/main.yml | 5 +- 3 files changed, 494 insertions(+), 379 deletions(-) delete mode 100644 roles/mediawiki/files/OpenIDConnect.class.php create mode 100644 roles/mediawiki/files/OpenIDConnect.php diff --git a/roles/mediawiki/files/OpenIDConnect.class.php b/roles/mediawiki/files/OpenIDConnect.class.php deleted file mode 100644 index 035d989d13..0000000000 --- a/roles/mediawiki/files/OpenIDConnect.class.php +++ /dev/null @@ -1,376 +0,0 @@ -get( 'iss' ); - - if ( !is_null( $iss ) ) { - - if ( isset( $_REQUEST['code'] ) && isset( $_REQUEST['status'] ) ) { - $session->remove( 'iss' ); - } - - if ( isset( $GLOBALS['wgOpenIDConnect_Config'][$iss] ) ) { - - $config = $GLOBALS['wgOpenIDConnect_Config'][$iss]; - - if ( !isset( $config['clientID'] ) || - !isset( $config['clientsecret'] ) ) { - wfDebug( "OpenID Connect: clientID or clientsecret not set for " . $iss ); - $params = [ - "uri" => urlencode( $_SERVER['REQUEST_URI'] ), - "query" => urlencode( $_SERVER['QUERY_STRING'] ) - ]; - self::redirect( "Special:SelectOpenIDConnectIssuer", - $params, true ); - return false; - } - - } else { - wfDebug( 'Issuer ' . $iss . - ' does not exist in wgOpeIDConnect_Config'. PHP_EOL ); - return false; - } - - } else { - - $iss_count = count( $GLOBALS['wgOpenIDConnect_Config'] ); - - if ( $iss_count < 1 ) { - return false; - } - - if ( $iss_count == 1 ) { - - $iss = array_keys( $GLOBALS['wgOpenIDConnect_Config'] ); - $iss = $iss[0]; - - $values = array_values( $GLOBALS['wgOpenIDConnect_Config'] ); - $config = $values[0]; - - if ( !isset( $config['clientID'] ) || - !isset( $config['clientsecret'] ) ) { - wfDebug( "OpenID Connect: clientID or clientsecret not set for " . - $iss ); - return false; - } - - } else { - - $params = [ - "uri" => urlencode( $_SERVER['REQUEST_URI'] ), - "query" => urlencode( $_SERVER['QUERY_STRING'] ) - ]; - self::redirect( "Special:SelectOpenIDConnectIssuer", - $params, true ); - return false; - } - } - - $clientID = $config['clientID']; - $clientsecret = $config['clientsecret']; - - $oidc = new OpenIDConnectClient( $iss, $clientID, $clientsecret ); - 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 ( $oidc->authenticate() ) { - if(count($oidc->requestUserInfo('cla')) < 1) { - $errorMessage = 'You need to have at least CLA+1 (cla)'; - return false; - } - if(count($oidc->requestUserInfo('groups')) < 1) { - $errorMessage = 'You need to have at least CLA+1 (group)'; - return false; - } - - $preferred_username = - $oidc->requestUserInfo( "preferred_username" ); - $realname = $oidc->requestUserInfo( "name" ); - $email = $oidc->requestUserInfo( "email" ); - $this->subject = $oidc->requestUserInfo( 'sub' ); - $this->issuer = $oidc->getProviderURL(); - - $username = $this->getName( $this->subject, $this->issuer ); - if ( !is_null( $username ) ) { - return true; - } - - if( $GLOBALS['wgOpenIDConnect_MigrateUsersByEmail'] === true ) { - list ( $id, $username ) = $this->getMigratedIdByEmail( $email ); - if ( !is_null( $id ) ) { - $this->saveExtraAttributes( $id ); - wfDebug( "Migrated user " . $username . " by email: " . $email ); - return true; - } - } elseif ( $GLOBALS['wgOpenIDConnect_MigrateUsersByUserName'] === true ) { - $id = $this->getMigratedIdByUserName( $preferred_username ); - if ( !is_null( $id ) ) { - $this->saveExtraAttributes( $id ); - wfDebug( "Migrated user by username: " . $preferred_username ); - $username = $preferred_username; - return true; - } - } - - $username = self::getAvailableUsername( $preferred_username, - $realname, $email, $this->subject ); - - $authManager = Authmanager::singleton(); - $authManager->setAuthenticationSessionData( - OpenIDConnect::OIDC_SUBJECT_SESSION_KEY, $this->subject ); - $authManager->setAuthenticationSessionData( - OpenIDConnect::OIDC_ISSUER_SESSION_KEY, $this->issuer ); - return true; - - } else { - $session->clear(); - return false; - } - } catch ( Exception $e ) { - wfDebug( $e->__toString() . PHP_EOL ); - $session->clear(); - return false; - } - } - - /** - * @since 1.0 - * - * @param User &$user - */ - public function deauthenticate( User &$user ) { - if ( $GLOBALS['wgOpenIDConnect_ForceLogout'] === true ) { - $returnto = 'Special:UserLogin'; - $params = [ 'forcelogin' => 'true' ]; - self::redirect( $returnto, $params ); - } - return true; - } - - /** - * @since 1.0 - * - * @param $id - */ - public function saveExtraAttributes( $id ) { - $authManager = Authmanager::singleton(); - if ( is_null( $this->subject ) ) { - $this->subject = $authManager->getAuthenticationSessionData( - OpenIDConnect::OIDC_SUBJECT_SESSION_KEY ); - $authManager->removeAuthenticationSessionData( - OpenIDConnect::OIDC_SUBJECT_SESSION_KEY ); - } - if ( is_null( $this->issuer ) ) { - $this->issuer = $authManager->getAuthenticationSessionData( - OpenIDConnect::OIDC_ISSUER_SESSION_KEY ); - $authManager->removeAuthenticationSessionData( - OpenIDConnect::OIDC_ISSUER_SESSION_KEY ); - } - $dbw = wfGetDB( DB_MASTER ); - $dbw->update( 'user', - [ // SET - 'subject' => $this->subject, - 'issuer' => $this->issuer - ], [ // WHERE - 'user_id' => $id - ], __METHOD__ - ); - } - - private static function getName( $subject, $issuer ) { - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'user', - [ 'user_name' ], - [ - 'subject' => $subject, - 'issuer' => $issuer - ], __METHOD__ - ); - if ( $row === false ) { - return null; - } else { - return $row->user_name; - } - } - - private static function getMigratedIdByUserName( $username ) { - $nt = Title::makeTitleSafe( NS_USER, $username ); - if ( is_null( $nt ) ) { - return null; - } - $username = $nt->getText(); - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'user', - [ 'user_id' ], - [ - 'user_name' => $username, - 'subject' => null, - 'issuer' => null - ], - __METHOD__ - ); - if ( $row === false ) { - return null; - } else { - return $row->user_id; - } - } - - private static function getMigratedIdByEmail( $email ) { - wfDebug( "Matching user to email " . $email ); - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'user', - [ - 'user_id', - 'user_name' - ], - [ - 'user_email' => $email, - 'subject' => null, - 'issuer' => null - ], - __METHOD__, - [ - // if multiple matching accounts, use the oldest one - 'ORDER BY' => 'user_registration', - 'LIMIT' => 1 - ] - ); - if ( $row === false ) { - return [ null, null ]; - } else { - return [ $row->user_id, $row->user_name ]; - } - } - - private static function getAvailableUsername( $preferred_username, - $realname, $email, $subject ) { - if ( strlen( $preferred_username ) > 0 ) { - $name = $preferred_username; - } elseif ( strlen ( $realname ) > 0 && - $GLOBALS['wgOpenIDConnect_UseRealNameAsUserName'] === true ) { - $name = $realname; - } elseif ( strlen( $email ) > 0 && - $GLOBALS['wgOpenIDConnect_UseEmailNameAsUserName'] === true ) { - $pos = strpos ( $email, '@' ); - if ( $pos !== false && $pos > 0 ) { - $name = substr( $email, 0, $pos ); - } else { - $name = $email; - } - } - $nt = Title::makeTitleSafe( NS_USER, $name ); - if ( is_null( $nt ) ) { - $name = "User"; - } elseif ( is_null( User::idFromName( $name ) ) ) { - return $nt->getText(); - } else { - $name = $nt->getText(); - } - $count = 1; - while ( !is_null( User::idFromName( $name . $count ) ) ) { - $count++; - } - return $name . $count; - } - - private static function redirect( $page, $params = null, $doExit = false ) { - $title = Title::newFromText( $page ); - if ( is_null( $title ) ) { - $title = Title::newMainPage(); - } - $url = $title->getFullURL(); - if ( is_array( $params ) && count( $params ) > 0 ) { - foreach ( $params as $key => $value ) { - $url = wfAppendQuery( $url, $key . '=' . $value ); - } - } - header( 'Location: ' . $url ); - if ( $doExit ) { - exit; - } - } - - public static function loadExtensionSchemaUpdates( $updater ) { - $updater->addExtensionField( 'user', 'subject', - __DIR__ . '/AddSubject.sql' ); - $updater->addExtensionField( 'user', 'issuer', - __DIR__ . '/AddIssuer.sql' ); - return true; - } -} diff --git a/roles/mediawiki/files/OpenIDConnect.php b/roles/mediawiki/files/OpenIDConnect.php new file mode 100644 index 0000000000..7e6f012e3f --- /dev/null +++ b/roles/mediawiki/files/OpenIDConnect.php @@ -0,0 +1,492 @@ +get( 'iss' ); + + if ( $iss !== null ) { + + if ( isset( $_REQUEST['code'] ) && isset( $_REQUEST['status'] ) ) { + $session->remove( 'iss' ); + } + + if ( isset( $GLOBALS['wgOpenIDConnect_Config'][$iss] ) ) { + + $config = $GLOBALS['wgOpenIDConnect_Config'][$iss]; + + if ( !isset( $config['clientID'] ) || + !isset( $config['clientsecret'] ) ) { + wfDebugLog( 'OpenID Connect', + 'OpenID Connect: clientID or clientsecret not set for ' . $iss . + '.' . PHP_EOL ); + $params = [ + 'uri' => urlencode( $_SERVER['REQUEST_URI'] ), + 'query' => urlencode( $_SERVER['QUERY_STRING'] ) + ]; + self::redirect( 'Special:SelectOpenIDConnectIssuer', + $params, true ); + return false; + } + + } else { + wfDebugLog( 'OpenID Connect', 'Issuer ' . $iss . + ' does not exist in wgOpeIDConnect_Config.' . PHP_EOL ); + return false; + } + + } else { + + $iss_count = count( $GLOBALS['wgOpenIDConnect_Config'] ); + + if ( $iss_count < 1 ) { + return false; + } + + if ( $iss_count == 1 ) { + + $iss = array_keys( $GLOBALS['wgOpenIDConnect_Config'] ); + $iss = $iss[0]; + + $values = array_values( $GLOBALS['wgOpenIDConnect_Config'] ); + $config = $values[0]; + + if ( !isset( $config['clientID'] ) || + !isset( $config['clientsecret'] ) ) { + wfDebugLog( 'OpenID Connect', + 'OpenID Connect: clientID or clientsecret not set for ' . + $iss . '.' . PHP_EOL ); + return false; + } + + } else { + + $params = [ + 'uri' => urlencode( $_SERVER['REQUEST_URI'] ), + 'query' => urlencode( $_SERVER['QUERY_STRING'] ) + ]; + self::redirect( 'Special:SelectOpenIDConnectIssuer', + $params, true ); + return false; + } + } + + $clientID = $config['clientID']; + $clientsecret = $config['clientsecret']; + + $oidc = new OpenIDConnectClient( $iss, $clientID, $clientsecret ); + 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'] ); + } + $redirectURL = + SpecialPage::getTitleFor( 'PluggableAuthLogin' )->getFullURL(); + $oidc->setRedirectURL( $redirectURL ); + wfDebugLog( 'OpenID Connect', 'Redirect URL: ' . $redirectURL ); + if ( $oidc->authenticate() ) { + + if(in_array($oidc->requestUserInfo('agreements'), "FPCA")) { + $errorMessage = 'You need to have signed the FPCA'; + return false; + } + if(count($oidc->requestUserInfo('groups')) < 1) { + $errorMessage = 'You need to have signed the FPCA+1 group'; + return false; + } + + $realname = $oidc->requestUserInfo( 'name' ); + $email = $oidc->requestUserInfo( 'email' ); + $this->subject = $oidc->requestUserInfo( 'sub' ); + $this->issuer = $oidc->getProviderURL(); + wfDebugLog( 'OpenID Connect', 'Real name: ' . $realname . + ', Email: ' . $email . ', Subject: ' . $this->subject . + ', Issuer: ' . $this->issuer ); + + list( $id, $username ) = + $this->findUser( $this->subject, $this->issuer ); + if ( $id !== null ) { + wfDebugLog( 'OpenID Connect', + 'Found user with matching subject and issuer.' . PHP_EOL ); + return true; + } + + wfDebugLog( 'OpenID Connect', + 'No user found with matching subject and issuer.' . PHP_EOL ); + + if ( $GLOBALS['wgOpenIDConnect_MigrateUsersByEmail'] === true ) { + wfDebugLog( 'OpenID Connect', 'Checking for email migration.' . + PHP_EOL ); + list( $id, $username ) = $this->getMigratedIdByEmail( $email ); + if ( $id !== null ) { + $this->saveExtraAttributes( $id ); + wfDebugLog( 'OpenID Connect', 'Migrated user ' . $username . + ' by email: ' . $email . '.' . PHP_EOL ); + return true; + } + } + + $preferred_username = $this->getPreferredUsername( $config, $oidc, + $realname, $email ); + wfDebugLog( 'OpenID Connect', 'Preferred username: ' . + $preferred_username . PHP_EOL ); + + if ( $GLOBALS['wgOpenIDConnect_MigrateUsersByUserName'] === true ) { + wfDebugLog( 'OpenID Connect', 'Checking for username migration.' . + PHP_EOL ); + $id = $this->getMigratedIdByUserName( $preferred_username ); + if ( $id !== null ) { + $this->saveExtraAttributes( $id ); + wfDebugLog( 'OpenID Connect', 'Migrated user by username: ' . + $preferred_username . '.' . PHP_EOL ); + $username = $preferred_username; + return true; + } + } + + $username = self::getAvailableUsername( $preferred_username, + $realname, $email ); + + wfDebugLog( 'OpenID Connect', 'Available username: ' . + $username . PHP_EOL ); + + $authManager = Authmanager::singleton(); + $authManager->setAuthenticationSessionData( + self::OIDC_SUBJECT_SESSION_KEY, $this->subject ); + $authManager->setAuthenticationSessionData( + self::OIDC_ISSUER_SESSION_KEY, $this->issuer ); + return true; + } + + } catch ( Exception $e ) { + wfDebugLog( 'OpenID Connect', $e->__toString() . PHP_EOL ); + $errorMessage = $e->__toString(); + $session->clear(); + return false; + } + } + + /** + * @since 1.0 + * + * @param User &$user + */ + public function deauthenticate( User &$user ) { + if ( $GLOBALS['wgOpenIDConnect_ForceLogout'] === true ) { + $returnto = 'Special:UserLogin'; + $params = [ 'forcelogin' => 'true' ]; + self::redirect( $returnto, $params ); + } + } + + /** + * @since 1.0 + * + * @param int $id user id + */ + public function saveExtraAttributes( $id ) { + $authManager = Authmanager::singleton(); + if ( $this->subject === null ) { + $this->subject = $authManager->getAuthenticationSessionData( + self::OIDC_SUBJECT_SESSION_KEY ); + $authManager->removeAuthenticationSessionData( + self::OIDC_SUBJECT_SESSION_KEY ); + } + if ( $this->issuer === null ) { + $this->issuer = $authManager->getAuthenticationSessionData( + self::OIDC_ISSUER_SESSION_KEY ); + $authManager->removeAuthenticationSessionData( + self::OIDC_ISSUER_SESSION_KEY ); + } + $dbw = wfGetDB( DB_MASTER ); + $dbw->upsert( + 'openid_connect', + [ + 'oidc_user' => $id, + 'oidc_subject' => $this->subject, + 'oidc_issuer' => $this->issuer + ], + [ + [ 'oidc_user' ] + ], + [ + 'oidc_subject' => $this->subject, + 'oidc_issuer' => $this->issuer + ], + __METHOD__ + ); + } + + private static function findUser( $subject, $issuer ) { + $dbr = wfGetDB( DB_REPLICA ); + $row = $dbr->selectRow( + [ + 'user', + 'openid_connect' + ], + [ + 'user_id', + 'user_name' + ], + [ + 'oidc_subject' => $subject, + 'oidc_issuer' => $issuer + ], + __METHOD__, + [], + [ + 'openid_connect' => [ 'JOIN', [ 'user_id=oidc_user' ] ] + ] + ); + if ( $row === false ) { + return [ null, null ]; + } else { + return [ $row->user_id, $row->user_name ]; + } + } + + private static function getPreferredUsername( $config, $oidc, $realname, + $email ) { + if ( isset( $config['preferred_username'] ) ) { + wfDebugLog( 'OpenID Connect', '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 && + $GLOBALS['wgOpenIDConnect_UseRealNameAsUserName'] === true ) { + $preferred_username = $realname; + } elseif ( strlen( $email ) > 0 && + $GLOBALS['wgOpenIDConnect_UseEmailNameAsUserName'] === true ) { + $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( 'OpenID Connect', + 'Invalid preferred username for migration: ' . $username . '.' . + PHP_EOL ); + return null; + } + $username = $nt->getText(); + $dbr = wfGetDB( DB_REPLICA ); + $row = $dbr->selectRow( + [ + 'user', + 'openid_connect' + ], + [ + 'user_id' + ], + [ + 'user_name' => $username, + 'oidc_user' => null + ], + __METHOD__, + [], + [ + 'openid_connect' => [ 'LEFT JOIN', [ 'user_id=oidc_user' ] ] + ] + ); + if ( $row !== false ) { + return $row->user_id; + } + return null; + } + + private static function getMigratedIdByEmail( $email ) { + wfDebugLog( 'OpenID Connect', 'Matching user to email ' . $email . '.' . + PHP_EOL ); + $dbr = wfGetDB( DB_REPLICA ); + $row = $dbr->selectRow( + [ + 'user', + 'openid_connect' + ], + [ + 'user_id', + 'user_name', + 'oidc_user' + ], + [ + 'user_email' => $email + ], + __METHOD__, + [ + // if multiple matching accounts, use the oldest one + 'ORDER BY' => 'user_registration' + ], + [ + 'openid_connect' => [ '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; + } + + private static function redirect( $page, $params = [], $doExit = false ) { + $title = Title::newFromText( $page ); + if ( $title === null ) { + $title = Title::newMainPage(); + } + $url = $title->getFullURL( $params ); + header( 'Location: ' . $url ); + if ( $doExit ) { + exit; + } + } + + /** + * Implements LoadExtensionSchemaUpdates hook. + * + * @param DatabaseUpdater $updater + */ + public static function loadExtensionSchemaUpdates( $updater ) { + $dir = $GLOBALS['wgExtensionDirectory'] . '/OpenIDConnect/sql/'; + $type = $updater->getDB()->getType(); + $updater->addExtensionTable( 'openid_connect', + $dir . $type . '/AddTable.sql' ); + $updater->addExtensionUpdate( [ [ __CLASS__, 'migrateSubjectAndIssuer' ], + $updater ] ); + } + + /** + * Migrate subject and issuer columns from user table to openid_connect + * table. + * + * @param DatabaseUpdater $updater + */ + public static function migrateSubjectAndIssuer( $updater ) { + if ( $updater->getDB()->fieldExists( 'user', 'subject', __METHOD__ ) && + $updater->getDB()->fieldExists( 'user', 'issuer', __METHOD__ ) ) { + $maintenance = new FakeMaintenance(); + $task = $maintenance->runChild( + 'MigrateOIDCSubjectAndIssuerFromUserTable' ); + if ( $task->execute() ) { + $dir = $GLOBALS['wgExtensionDirectory'] . '/OpenIDConnect/sql/'; + $type = $updater->getDB()->getType(); + $patch = $dir . $type . '/DropColumnsFromUserTable.sql'; + $updater->modifyField( 'user', 'subject', $patch, true ); + } + } else { + $updater->output( + '...user table does not have subject and issuer columns.' . PHP_EOL ); + } + } +} diff --git a/roles/mediawiki/tasks/main.yml b/roles/mediawiki/tasks/main.yml index 88ffe74f16..ce2dbcae15 100644 --- a/roles/mediawiki/tasks/main.yml +++ b/roles/mediawiki/tasks/main.yml @@ -214,9 +214,8 @@ - mediawiki - name: Patch OpenIDConnect plugin to require CLA+1 - when: env == "production" - copy: src=OpenIDConnect.class.php - dest=/srv/web/fp-wiki/extensions/OpenIDConnect/OpenIDConnect.class.php + copy: src=OpenIDConnect.php + dest=/srv/web/fp-wiki/extensions/OpenIDConnect/src/OpenIDConnect.php tags: - mediawiki