MediawikiShibAuthWithPersistentID

Innen: KIFÜ Wiki
A lap korábbi változatát látod, amilyen Sitya(AT)niif.hu (vitalap | szerkesztései) 2010. május 3., 10:06-kor történt szerkesztése után volt. (Ú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…”)
(eltér) ← Régebbi változat | Aktuális változat (eltér) | Újabb változat→ (eltér)

Extension:Shibboleth Authentication with persistent-id support

This extension is based on the original 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.

// 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();

ShibAuthPlugin.php

I rewrote the ShibUserLoadFromSession 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.


<?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);
}

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 SpecialRenameuser_body.php.

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 ) ) .