Módosítások

MediawikiShibAuthWithPersistentID

20 278 bájt hozzáadva, 2010. május 3., 11:06
Új oldal, tartalma: „=Extension:Shibboleth Authentication with persistent-id support= This extension is based on the original [http://www.mediawiki.org/wiki/Extension:Shibboleth_Authenticatio…”
=Extension:Shibboleth Authentication with persistent-id support=

This extension is based on the original [http://www.mediawiki.org/wiki/Extension:Shibboleth_Authentication Extension:Shibboleth Authentication], the basic information will not be copied, here you can find the differences and the explanation of these differences.

== LocalSettings.php ==

I made only a little change to make easier to configure the modul with different Shibboleth variable names, and set a working logout link.

<source lang="php">
// Shibboleth Authentication Stuff
// Load ShibAuthPlugin
require_once('extensions/ShibAuthPlugin.php');


// Last portion of the shibboleth WAYF url for lazy sessions.
// This value is found in your shibboleth.xml file on the setup for your SP
// WAYF url will look something like: /Shibboleth.sso/WAYF/$shib_WAYF
//$shib_WAYF = "idp.example.org";

//Are you using an old style WAYF (Shib 1.3) or new style Discover Service (Shib 2.x)?
//Values are WAYF or DS, defaults to WAYF
$shib_WAYFStyle = "DS";


// Is the assertion consumer service located at an https address (highly recommended)
// Default for compatibility with previous version: false
$shib_Https = true;

// Prompt for user to login
$shib_LoginHint = "Login via Single Sign-on";

// Where is the assertion consumer service located on the website?
// Default: "/Shibboleth.sso"
$shib_AssertionConsumerServiceURL = "/Shibboleth.sso";

//Config headernames
$SHIB_PERSISTENTID = 'persistent-id'; // persistent-id (eduPersonTargetedID)
$SHIB_EPPN = 'eppn'; // eduPersonPrincipalName
$SHIB_MAIL = 'mail'; // E-mail
$SHIB_CN = 'cn'; // Common Name

// Map persistent-id
$shib_persistentid = ( isset( $_SERVER[$SHIB_PERSISTENTID] ) ) ? $_SERVER[$SHIB_PERSISTENTID] : null;

// Map username
$shib_UN = ( isset( $_SERVER[$SHIB_EPPN] ) ) ? $_SERVER[$SHIB_EPPN] : null;

// Map real name
$shib_RN = ( isset( $_SERVER[$SHIB_CN] ) ) ? $_SERVER[$SHIB_CN] : null;

// Map e-mail
$shib_email = ( isset( $_SERVER[$SHIB_MAIL] ) ) ? $_SERVER[$SHIB_MAIL] : null;

// The ShibUpdateUser hook is executed on login.
// It has two arguments:
// - $existing: True if this is an existing user, false if it is a new user being added
// - &$user: A reference to the user object.
// $user->updateUser() is called after the function finishes.
// In the event handler you can change the user object, for instance set the email address or the real name
// The example function shown here should match behavior from previous versions of the extension:

$wgHooks['ShibUpdateUser'][] = 'ShibUpdateTheUser';

function ShibUpdateTheUser($existing, &$user) {
global $shib_email;
global $shib_RN;
if (! $existing) {
if($shib_email != null)
$user->setEmail($shib_email);
if($shib_RN != null)
$user->setRealName($shib_RN);
}
return true;
}

// Shibboleth doesn't really support logging out very well. To take care of
// this we simply get rid of the logout link when a user is logged in through
// Shib. Alternatively, you can uncomment and set the variable below to a link
// that will either clear the user's cookies or log the user out of the Idp and
// instead of deleting the logout link, the extension will change it instead.
$shib_logout = "/Shibboleth.sso/Logout?return=https://" . $_SERVER['SERVER_NAME'] .
"/%3Ftitle=Special:Userlogout&returnto=Main_Page";

// Activate Shibboleth Plugin
SetupShibAuth();
</source>

== ShibAuthPlugin.php ==

I rewrote the <code>ShibUserLoadFromSession</code> function, extended with a few new function, and comments. :) I did not change ShibAuthPlugin class, and made only a little changes in SetupShibAuth function.

I did not change the version information neither.



<source lang="php">

<?php

/**
* Version 1.2.3 (Works out of box with MW 1.13 or above)
*
* Authentication Plugin for Shibboleth (http://shibboleth.internet2.edu)
* Derived from AuthPlugin.php
* Much of the commenting comes straight from AuthPlugin.php
*
* Portions Copyright 2006, 2007 Regents of the University of California.
* Portions Copyright 2007, 2008 Steven Langenaken
* Released under the GNU General Public License
*
* Documentation at http://www.mediawiki.org/wiki/Extension:Shibboleth_Authentication
* Project IRC Channel: #sdcolleges on irc.freenode.net
*
* Extension Maintainer:
* * Steven Langenaken - Added assertion support, more robust https checking, bugfixes for lazy auth, ShibUpdateUser hook
* Extension Developers:
* * D.J. Capelis - Developed initial version of the extension
*/

require_once('AuthPlugin.php');

class ShibAuthPlugin extends AuthPlugin {
var $existingUser = false;

/**
* Check whether there exists a user account with the given name.
* The name will be normalized to MediaWiki's requirements, so
* you might need to munge it (for instance, for lowercase initial
* letters).
*
* @param string $username
* @return bool
* @access public
*/
function userExists( $username ) {
return true;
}


/**
* Check if a username+password pair is a valid login.
* The name will be normalized to MediaWiki's requirements, so
* you might need to munge it (for instance, for lowercase initial
* letters).
*
* @param string $username
* @param string $password
* @return bool
* @access public
*/
function authenticate( $username, $password) {

global $shib_UN;
return $username == $shib_UN;

}

/**
* Modify options in the login template.
*
* @param UserLoginTemplate $template
* @access public
*/
function modifyUITemplate( &$template ) {
$template->set('useemail', false);
$template->set('remember', false);
$template->set('domain', false);
$template->set( 'usedomain', false );
}

/**
* Set the domain this plugin is supposed to use when authenticating.
*
* @param string $domain
* @access public
*/
function setDomain( $domain ) {
$this->domain = $domain;
}

/**
* Check to see if the specific domain is a valid domain.
*
* @param string $domain
* @return bool
* @access public
*/
function validDomain( $domain ) {
return true;
}

/**
* When a user logs in, optionally fill in preferences and such.
* For instance, you might pull the email address or real name from the
* external user database.
*
* The User object is passed by reference so it can be modified; don't
* forget the & on your function declaration.
*
* @param User $user
* @access public
*/
function updateUser( &$user ) {
wfRunHooks('ShibUpdateUser', array($this->existingUser, $user));

//For security, set password to a non-existant hash.
if ($user->mPassword != "nologin") {
$user->mPassword = "nologin";
}

$user->setOption('rememberpassword', 0);
$user->saveSettings();
return true;
}


/**
* Return true if the wiki should create a new local account automatically
* when asked to login a user who doesn't exist locally but does in the
* external auth database.
*
* If you don't automatically create accounts, you must still create
* accounts in some way. It's not possible to authenticate without
* a local account.
*
* This is just a question, and shouldn't perform any actions.
*
* @return bool
* @access public
*/
function autoCreate() {
return true;
}

/**
* Can users change their passwords?
*
* @return bool
*/
function allowPasswordChange() {
global $shib_pretend;

return $shib_pretend;

}

/**
* Set the given password in the authentication database.
* Return true if successful.
*
* @param string $password
* @return bool
* @access public
*/
function setPassword( $password ) {
global $shib_pretend;

return $shib_pretend;
}

/**
* Update user information in the external authentication database.
* Return true if successful.
*
* @param User $user
* @return bool
* @access public
*/
function updateExternalDB( $user ) {
//Not really, but wiki thinks we did...
return true;
}

/**
* Check to see if external accounts can be created.
* Return true if external accounts can be created.
* @return bool
* @access public
*/
function canCreateAccounts() {
return false;
}

/**
* Add a user to the external authentication database.
* Return true if successful.
*
* @param User $user
* @param string $password
* @return bool
* @access public
*/
function addUser( $user, $password ) {
return false;
}


/**
* Return true to prevent logins that don't authenticate here from being
* checked against the local database's password fields.
*
* This is just a question, and shouldn't perform any actions.
*
* @return bool
* @access public
*/
function strict() {
return false;
}

/**
* When creating a user account, optionally fill in preferences and such.
* For instance, you might pull the email address or real name from the
* external user database.
*
* The User object is passed by reference so it can be modified; don't
* forget the & on your function declaration.
*
* @param User $user
* @access public
*/
function initUser( &$user, $autocreate ) {
$this->updateUser($user);
}

/**
* If you want to munge the case of an account name before the final
* check, now is your chance.
*/
function getCanonicalName( $username ) {
return $username;
}
}

function ShibGetAuthHook() {

global $wgVersion;
global $wgUser;

if ($wgVersion >= "1.13") {
if ($wgUser) {
return 'UserLoadAfterLoadFromSession';
} else {
return 'UserLoadFromSession';
}
} else {
return 'AutoAuthenticate';
}

}

/*
* End of AuthPlugin Code, beginning of hook code and auth functions
*/

$wgExtensionFunctions[] = 'SetupShibAuth';
$wgExtensionCredits['other'][] = array(
'name' => 'Shibboleth Authentication',
'version' => '1.2.3',
'author' => "Regents of the University of California, Steven Langenaken",
'url' => "http://www.mediawiki.org/wiki/Extension:Shibboleth_Authentication",
'description' => "Allows logging in through Shibboleth",
);

function SetupShibAuth() {
global $shib_UN;
global $shib_persistentid;

global $wgHooks;
global $wgAuth;
global $wgCookieExpiration;

if ( $shib_persistentid == null && $shib_UN == null ) {

$wgHooks['PersonalUrls'][] = 'ShibLinkAdd';

} else {

$wgCookieExpiration = -3600;
$wgHooks[ShibGetAuthHook()][] = "ShibUserLoadFromSession";
$wgHooks['PersonalUrls'][] = 'ShibActive'; /* Disallow logout link */
$wgAuth = new ShibAuthPlugin();


}

}

function ShibGetUserIDFromPersistentID( $persistentID ) {

$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->selectRow( "user_persistentid", array( "userID" ), array( "persistentID" => $persistentID ) );
return ( $res ) ? $res->userID : false;
}


function ShibAddUserPersistentID( $userID, $persistentID ) {

$dbr = wfGetDB( DB_MASTER );
return $dbr->insert( "user_persistentid", array( "userID" => $userID, "persistentID" => $persistentID ) );

}

function ifUserAlreadyInLocalDB ( $username ) {
return ( User::idFromName( $username ) != null && User::idFromName( $username ) != 0 ) ? User::idFromName( $username ) : false;
}

function setupUserAlreadyInLocalDB ( $username ) {

global $wgAuth;

$user = User::newFromName( ucfirst( $username ) );
$user->load();
$wgAuth->existingUser = true;
$wgAuth->updateUser( $user ); //Make sure password is nologin
$user->setupSession();
$user->setCookies();

return true;

}

function addUserLocalDBwithBlackMagic ( &$user, $username ) {

global $shib_pretend;
global $wgRedirectOnLogin;

/*
* Since we only get called when someone should be logged in, if they
* aren't let's make that happen. Oddly enough the way MW does all
* this is simply to use a loginForm class that pretty much does
* most of what you need. Creating a loginform is a very very small
* part of this object.
*/
require_once('specials/SpecialUserlogin.php');

//This section contains a silly hack for MW
global $wgLang;
global $wgContLang;
global $wgRequest;
$wgLangUnset = false;

if(!isset($wgLang)) {
$wgLang = $wgContLang;
$wgLangUnset = true;
}

ShibKillAA();

//This creates our form that'll do black magic
$lf = new LoginForm($wgRequest);

//Place the hook back (Not strictly necessarily MW Ver >= 1.9)
//ShibBringBackAA();

//And now we clean up our hack
if($wgLangUnset == true) {
unset($wgLang);
unset($wgLangUnset);
}

//Okay, kick this up a notch then...
$user->setName( ucfirst( $username ) );

//The mediawiki developers entirely broke use of this the
//straightforward way in 1.9, so now we just lie...
$shib_pretend = true;

//Now we _do_ the black magic
$lf->mRemember = false;
$user->loadDefaults( ucfirst( $username ) );
$lf->initUser($user, true);

//Stop pretending now
$shib_pretend = false;

//Finish it off
$user->saveSettings();
$user->setupSession();
$user->setCookies();
$lf->successfulLogin();
return true;
}

/* Tries to be magical about when to log in users and when not to. */
function ShibUserLoadFromSession($user, &$result = true) {
global $shib_persistentid;
global $shib_UN;
global $wgRedirectOnLogin;


ShibKillAA();

//For versions of mediawiki which enjoy calling AutoAuth with null users
if ($user === null) {
$user = User::loadFromSession();
}

//They already with us? If so, nix this function, we're good.
if($user->isLoggedIn()) {

ShibBringBackAA();
return true;
}


if ( $shib_persistentid != null && $shib_UN == null ) {

// If the user only has persistentID, and his persistentID has not been in the DB yet,
// he will be handled as new user, even if he had local id created from username earlier.
// It is not possible to link the new account persID account with the old one without $shib_UN

if ( $userID = ShibGetUserIDFromPersistentID( $shib_persistentid ) ) {

//We know his persistentID and he has a linked local account

return setupUserAlreadyInLocalDB( User::whoIs( $userID ));

} else {

// We have to choose a local name for the new user, or develop
// to be able to modify userCreateTemplate and redirect the user there

$tempName = "TEMPUserName".substr(date("YssHIs")*rand(1,30)+"10101",0,10);
$wgRedirectOnLogin = "Special:RenameUser";

// Add and load the new user
addUserLocalDBwithBlackMagic( $user, $tempName );

// Add the persistentID
ShibAddUserPersistentID( ifUserAlreadyInLocalDB( $tempName ), $shib_persistentid );

return true;

}

} elseif ( $shib_persistentid != null && $shib_UN != null ) {

if ( ShibGetUserIDFromPersistentID( $shib_persistentid ) == ifUserAlreadyInLocalDB( $shib_UN ) ) {

// If so, the user has a local account and it has been already
// linked with his persistentID, so he can log in
return setupUserAlreadyInLocalDB( $shib_UN );

} elseif ( ShibAddUserPersistentID( ifUserAlreadyInLocalDB( $shib_UN ), $shib_persistentid ) ) {

// If we made the link between local account and persistentID
// then the user can log in

return setupUserAlreadyInLocalDB( $shib_UN );

} else {

// A brand new user
addUserLocalDBwithBlackMagic( $user, $shib_UN );

// If we managed to add a new local user, we have to link to the persistentID
ShibAddUserPersistentID( ifUserAlreadyInLocalDB( $shib_UN ), $shib_persistentid );

return true;

}

} elseif ( $shib_persistentid == null && $shib_UN != null ) {

//The old way...
if ( ifUserAlreadyInLocalDB( $shib_UN ) ) {

return setupUserAlreadyInLocalDB( $shib_UN );

} else {

return addUserLocalDBwithBlackMagic ( $user, $shib_UN );

}

}

return false;

}

function ShibKillAA() {
global $wgHooks;

//Temporarily kill The AutoAuth Hook to prevent recursion
foreach ($wgHooks[ShibGetAuthHook()] as $key => $value) {
if($value == "Shib".ShibGetAuthHook())
$wgHooks[ShibGetAuthHook()][$key] = 'ShibBringBackAA';
}
}
/* Puts the auto-auth hook back into the hooks array */
function ShibBringBackAA() {
global $wgHooks;

foreach ($wgHooks[ShibGetAuthHook()] as $key => $value) {
if($value == 'ShibBringBackAA')
$wgHooks[ShibGetAuthHook()][$key] = "Shib".ShibGetAuthHook();
}
return true;
}

/* Add login link */
function ShibLinkAdd(&$personal_urls, $title) {
global $shib_WAYF, $shib_LoginHint, $shib_Https, $shib_AssertionConsumerServiceURL;
global $shib_WAYFStyle;
if (! isset($shib_AssertionConsumerServiceURL) || $shib_AssertionConsumerServiceURL == '')
$shib_AssertionConsumerServiceURL = "/Shibboleth.sso";
if (! isset($shib_Https))
$shib_Https = false;
if (! isset($shib_WAYFStyle))
$shib_WAYFStyle = 'DS';
if ($shib_WAYFStyle == 'DS')
$shib_ConsumerPrefix = 'DS';
else
$shib_ConsumerPrefix = '';
$pageurl = $title->getLocalUrl();
if (! isset($shib_LoginHint))
$shib_LoginHint = "Login via Single Sign-on";

$personal_urls['SSOlogin'] = array(
'text' => $shib_LoginHint,
'href' => ($shib_Https ? 'https' : 'http') .'://' . $_SERVER['HTTP_HOST'] .
$shib_AssertionConsumerServiceURL . "/" . $shib_ConsumerPrefix . $shib_WAYF .
'?target=' . (isset($_SERVER['HTTPS']) ? 'https' : 'http') .
'://' . $_SERVER['HTTP_HOST'] . $pageurl, );
return true;
}

/* Kill logout link */
function ShibActive(&$personal_urls, $title) {
global $shib_logout;
global $shib_RN;
global $shib_map_info;

if($shib_logout == null)
$personal_urls['logout'] = null;
else
$personal_urls['logout']['href'] = $shib_logout;

if ($shib_RN && $shib_map_info)
$personal_urls['userpage']['text'] = $shib_RN;

return true;
}

function ShibAutoAuthenticate(&$user) {
ShibUserLoadAfterLoadFromSession($user);
}



</source>

== ToDo ==

==After login==

If the user only has persistent-id, and it is the first time to login, he is given a temporary username, so he will be supposed to change it. To change username mediawiki needs an extension, called RenameUser.

We have to make a small modification on the extension. You can see the patch below, and download from here for the <code>SpecialRenameuser_body.php</code>.

<source lang="diff">
77,78c77,78
< <td class='mw-input'><i>" . $wgUser->mName . "</i>" .
< Xml::input( 'oldusername', 20, $wgUser->mName, array( 'type' => 'hidden' ) ) . ' ' .
---
> <td class='mw-input'>" .
> Xml::input( 'oldusername', 20, $oun, array( 'type' => 'text', 'tabindex' => '1' ) ) . ' ' .
86c86,94
< Xml::input( 'newusername', 20, $nun, array( 'type' => 'text', 'tabindex' => '1' ) ) .
---
> Xml::input( 'newusername', 20, $nun, array( 'type' => 'text', 'tabindex' => '2' ) ) .
> "</td>
> </tr>
> <tr>
> <td class='mw-label'>" .
> Xml::label( wfMsg( 'renameuserreason' ), 'reason' ) .
> "</td>
> <td class='mw-input'>" .
> Xml::input( 'reason', 40, $reason, array( 'type' => 'text', 'tabindex' => '3', 'maxlength' => 255 ) ) .

</source>

Navigációs menü