Extension:Shibboleth Authentication
![]() |
This extension stores its source code on a wiki page. Please be aware that this code may be unreviewed or maliciously altered. They may contain security holes, outdated interfaces that are no longer compatible etc. Note: No localisation updates are provided for this extension by translatewiki.net. |
Shibboleth Authentication Release status: stable |
|
---|---|
Implementation | User identity |
Description | Allows integration of mediawiki with the Shibboleth Single Sign-on project from Internet2. |
Author(s) | D.J. Capelis, Steven Langenaken (Steveltalk) |
Latest version | 1.2.4 (28 February 2013) |
MediaWiki | 1.7 or higher |
License | GPL |
Download | Extension Page |
Translate the Shibboleth Authentication extension if it is available at translatewiki.net |
|
Check usage and version matrix; code metrics |
Contents
- 1 Overview
- 2 Installation Instructions
- 3 Using the Extension
- 4 Tips, Tricks and Known Bugs
- 5 Extension Code
- 6 Changelog
- 6.1 Differences between 1.2.5 and 1.2.4
- 6.2 Differences between 1.2.4 and 1.2.3
- 6.3 Differences between 1.2.3 and 1.2.2
- 6.4 Differences between 1.2.2 and 1.2.1
- 6.5 Differences between 1.2.1 and 1.2.0
- 6.6 Differences between 1.2.0 and 1.1.8
- 6.7 Differences between 1.1.8 and 1.1.7
- 6.8 Differences between 1.1.7 and 1.1.6
- 6.9 Differences between 1.1.6 and 1.1.5
- 6.10 Differences between 1.1.5 and 1.1.4
- 6.11 Differences between 1.1.4 and 1.1.3
- 6.12 Differences between 1.1.3 and 1.1.2
- 6.13 Differences between 1.1.2 and 1.1.1
- 6.14 Differences between 1.1.1 and 1.1
- 6.15 Differences between 1.1 and 1.0
- 7 Acknowledgements
Overview[edit | edit source]
Shibboleth is a Single Signon identity management solution that's a project of Internet2. It's in use not only in many universities through the US and elsewhere but also on sites such as openidp to provide authentication to a range of applications. This extension allows mediawiki to use Shibboleth as an external authentication source.
Current Version: 1.2.4 (compatible with MediaWiki 1.20)
Installation Instructions[edit | edit source]
Compatibility[edit | edit source]
This extension is designed for MediaWiki 1.7 and up. If you want to use this extension with the 1.6 branch or earlier look at bugs 5819 and 6006 and apply the SVN changes that took place.
Shibboleth Configuration[edit | edit source]
The extension requires that shibboleth be configured and set up on the web server the wiki is hosted on. Once Shibboleth is running on the web server make sure your configuration file is correctly set-up. Your IdP should be able to help you with this. The extension uses lazy sessions and a WAYF url so you'll want to verify these are setup as well. (You can actually use full sessions if you want, but the extension is designed to use lazy sessions to allow for the standard wiki auth to co-exist alongside.) You'll need to tell the extension which WAYF url to use.
The part of the configuration file (shibboleth.xml) where a WAYF url will be present is here:
... <Sessions lifetime="7200" timeout="3600" checkAddress="false" consistentAddress="true" handlerURL="/Shibboleth.sso" handlerSSL="false" idpHistory="true" idpHistoryDays="7"> <SessionInitiator isDefault="true" id="example" Location="/WAYF/idp.example.org" Binding="urn:mace:shibboleth:sp:1.3:SessionInit" wayfURL="https://idp.example.org/PATH/TO/AUTH" wayfBinding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"/
The part of the configuration file that allows lazy sessions is here: (False is for lazy sessions, true for mandatory sessions.)
... <Host name="host.example.org"> <Path name="wikipath" authType="shibboleth" requireSession="false"/>
For lazy sessions in Apache, the following lines in your apache configuration files will do:
... <Location /pathname> AuthType shibboleth Require shibboleth </Location>
If you don't want lazy sessions but instead would rather require shibboleth authentication, then use:
... <Location /pathname> AuthType shibboleth ShibRequireSession On Require valid-user </Location>
Download the Extension[edit | edit source]
The code for the extension is at the bottom of this page. Place it in the extensions folder, in a file called "ShibAuthPlugin.php."
Extension Configuration[edit | edit source]
Now it's time to configure and load the extension. To do that, just add the following lines to LocalSettings.php in the root of the mediawiki directory.
Most of the configuration directives are of the form:
$shib_SOMELETTERS = data_manipulation_functions($_SERVER['HEADER_FOR_SHIB_DATA']);
This allows you to map whatever shibboleth identifiers you chose to a variety of fields for each user as well as use the standard PHP functions to massage the data as it enters. All the Shibboleth data has been placed in the $_SERVER superglobal by the Shibboleth module and should be available. You may need to run a script like the following to determine exactly what data is available:
<?php //Drop this into a directory where sessions are required //or where you are already authenticated and then simply //access it to see the mapping. while(list($index, $data) = each($_SERVER)) { echo "$index -> $data<br><br>"; } ?>
At the very minimum you'll need to make the following changes:
- Place the following code into the LocalSettings.php file.
- Set the WAYF url.
- Map a valid piece of Shib data for the username.
- If one attribute of the Shib session contains groups the user belongs to, map this attribute to the shib_groups variable.
- Look over the rest of the variables and ensure that you don't want to make any more changes.
// 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 = "WAYF"; // 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"; // Map Real Name to what Shibboleth variable(s)? # $shib_RN = ucfirst(strtolower($_SERVER['HTTP_FIRST_NAME'])) . ' ' # . ucfirst(strtolower($_SERVER['HTTP_LAST_NAME'])); // Map e-mail to what Shibboleth variable? # $shib_email = $_SERVER['HTTP_EMAIL']; // Field containing groups for the user and field containing the prefix to be searched (and stripped) from wiki groups # $shib_groups = $_SERVER['isMemberOf']; # $shib_group_prefix = "wiki"; // 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); # } #} // This is required to map to something // You should beware of possible namespace collisions, it is best to chose // something that will not violate MW's usual restrictions on characters // Map Username to what Shibboleth variable? $shib_UN = strtolower($_SERVER['HTTP_EMAIL']); // 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 = "http://example.org"; // Activate Shibboleth Plugin SetupShibAuth();
Using the Extension[edit | edit source]
The extension is designed to be fairly simple to use. Once everything is configured and Shibboleth is running a link will appear at the top right hand corner of the screen next to the normal mediawiki login link that says "login via single sign-on." Clicking this link will bounce the user to the WAYF url which will pass them to the IdP for authentication. When they come back they'll be logged in.
Tips, Tricks and Known Bugs[edit | edit source]
Adding more Metadata[edit | edit source]
If you wish to add more mappings from Mediawiki data to Shibboleth data than is available, you can simply add them in the configuration file. You just have to add the code changes in the event handler or add an extra event handler (ShibUpdateTheUser in the example).
Logout Functionality[edit | edit source]
Shibboleth currently has no good way to log people out. By default logout is disabled once people sign in through Shibboleth. Alternatively you may point logout to a url provided by your local IdP which will allow logout. If your local IdP doesn't support this and you really would like to log people out, simply create a small php script to unset the Shibboleth cookies and point people towards that, then redirect them to the standard mediawiki logout script.
Username Munging[edit | edit source]
While the extension will enforce the rule that all usernames must begin with a capital letter, it will not enforce any of the rest of mediawiki's usual rules for usernames. If your Shibboleth data will break other mediawiki username rules you should filter that out at the configuration level when $shib_UN gets set. In addition, if your shibboleth data includes sets of usernames that may only be separated by difference of upper and lower-case initial letters (Like "[email protected]" and "[email protected]") then the plugin will treat these two as if they were not distinct. Again the solution is to provide additional filtering in LocalSettings.php. By default we simply use the user's e-mail addresses converting any letters to lowercase and then pass those in. (Again, the extension takes care of converting the initial letter to uppercase.)
Extension Code[edit | edit source]
<?php /** * Version 1.2.5 (Works out of box with MW 1.7 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('includes/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, &$type ) { $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( $user, $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, $email = '', $realname = '' ) { 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 = false ) { $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; if ($wgVersion >= "1.13") { 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.4', '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 $wgHooks; global $wgAuth; global $wgCookieExpiration; if($shib_UN != null){ $wgCookieExpiration = -3600; $wgHooks[ShibGetAuthHook()][] = "Shib".ShibGetAuthHook(); $wgHooks['PersonalUrls'][] = 'ShibActive'; /* Disallow logout link */ $wgAuth = new ShibAuthPlugin(); } else { $wgHooks['PersonalUrls'][] = 'ShibLinkAdd'; } } /* 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 = 'WAYF'; if ($shib_WAYFStyle == 'WAYF') $shib_ConsumerPrefix = 'WAYF/'; else $shib_ConsumerPrefix = ''; $pageurl = $title->getLocalUrl(); if (! isset($shib_LoginHint)) $shib_LoginHint = "Login via Single Sign-on"; if ($shib_WAYFStyle == "Login") { $personal_urls['SSOlogin'] = array( 'text' => $shib_LoginHint, 'href' => ($shib_Https ? 'https' : 'http') .'://' . $_SERVER['HTTP_HOST'] . $shib_AssertionConsumerServiceURL . "/" . $shib_ConsumerPrefix . $shib_WAYFStyle . '?target=' . (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $pageurl, ); } elseif ($shib_WAYFStyle == "CustomLogin") { $personal_urls['SSOlogin'] = array( 'text' => $shib_LoginHint, 'href' => ($shib_Https ? 'https' : 'http') .'://' . $_SERVER['HTTP_HOST'] . $shib_AssertionConsumerServiceURL . '?target=' . (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $pageurl, ); } else { $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) { ShibUserLoadFromSession($user, true); } /* Tries to be magical about when to log in users and when not to. */ function ShibUserLoadFromSession($user, &$result) { global $wgContLang; global $wgAuth; global $shib_UN; global $wgHooks; global $shib_map_info; global $shib_map_info_existing; global $shib_pretend; global $shib_groups; 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; } //Is the user already in the database? if (User::idFromName($shib_UN) != null && User::idFromName($shib_UN) != 0) { $user = User::newFromName($shib_UN); $user->load(); $wgAuth->existingUser = true; $wgAuth->updateUser($user); //Make sure password is nologin wfSetupSession(); $user->setCookies(); ShibAddGroups($user); return true; } //Place the hook back (Not strictly necessarily MW Ver >= 1.9) ShibBringBackAA(); //Okay, kick this up a notch then... $user->setName($wgContLang->ucfirst($shib_UN)); /* * 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('includes/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); } //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($shib_UN); $lf->initUser($user, true); //Stop pretending now $shib_pretend = false; //Finish it off $user->saveSettings(); //$user->setupSession(); wfSetupSession(); $user->setCookies(); ShibAddGroups($user); return true; } function ShibAddGroups($user) { global $shib_groups; global $shib_group_prefix; $oldGroups = $user->getGroups(); foreach ($oldGroups as $group) { $user->removeGroup($group); } if (isset($shib_groups)) { foreach (explode(';', $shib_groups) as $group) { if (isset($shib_group_prefix) && !empty($shib_group_prefix)) { $vals = explode(":", $group); if ($vals[0] == "wiki") { $user->addGroup($vals[1]); } } else { $user->addGroup($group); } } } } function ShibKillAA() { global $wgHooks; # global $wgAuth; //looks unuseful here //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; # global $wgAuth; //looks unuseful here foreach ($wgHooks[ShibGetAuthHook()] as $key => $value) { if($value == 'ShibBringBackAA') $wgHooks[ShibGetAuthHook()][$key] = "Shib".ShibGetAuthHook(); } return true; } ?>
Changelog[edit | edit source]
Differences between 1.2.5 and 1.2.4[edit | edit source]
- Implemented the management of the attribute containing user groups (usually isMemberOf) obtained from Shibboleth session.
Differences between 1.2.4 and 1.2.3[edit | edit source]
- updated for MW 1.20
Differences between 1.2.3 and 1.2.2[edit | edit source]
- function ShibAuthAuthenticate was full of typos
- setup_session error (thanks to Dhess)
- Support for Shib 2.x (thanks to Barry Johnson, Clemson University)
Differences between 1.2.2 and 1.2.1[edit | edit source]
- Compatibility fix for MW 1.13: Automatic user creation didn't work any more
Differences between 1.2.1 and 1.2.0[edit | edit source]
- Functions renamed so they start with Shib (to prevent naming conflicts)
- Compatibility fix for MW 1.13: the AutoAuthenticate hook has been replaced by the UserLoadFromSession hook. The extension checks the version and registers the appropriate hook.
Differences between 1.2.0 and 1.1.8[edit | edit source]
- The configuration for this version is not backwards compatible with the previous version if $shib_map_info was set to true (search for ShibUpdateUser on this page).
What's new in this version:
- $shib_map_info is no longer used, nor are $shib_RN and $shib_email (directly).
- Mapping information from shibboleth to mediawiki now happens in event handlers for the ShibUpdateUser-hook instead of the updateUser-function itself.
- A security bug was fixed that caused users to remain logged in even after closing their browser when lazy authentication is used
- $wgCookieExpiration is made negative so when the user comes back after closing the browser, the homepage doesn't come from the per-user-cache but from the anonymous-user cache.
- Added extensionCredits to show the plugin on the Special:Version page
Differences between 1.1.8 and 1.1.7[edit | edit source]
- another forgotten Compatibility fix or MW 1.12
Differences between 1.1.7 and 1.1.6[edit | edit source]
- Compatibility fixes for MW 1.12 release: Hooks return true to continue other hook processing
Differences between 1.1.6 and 1.1.5[edit | edit source]
- Compatibility fixes for upcoming MW 1.10.0 release. (They broke the plugin again :-\)
- Fix error in some versions of php and MW that caused the plugin to fail
- Fix PHP notice about an undefined variable
- When $shib_RN is not null and $shib_map_info is set to true the plugin will now display the user's name in their URLs instead of their username. This looks pretty slick, but may need to be more configurable in the future.
Differences between 1.1.5 and 1.1.4[edit | edit source]
- Some of the cleanups in 1.1.4 blocked an untested codepath and prevented the plugin from working
Differences between 1.1.4 and 1.1.3[edit | edit source]
- Security Fix: Namespace collisions are now handled by clobbering the old user
- Cleanup for setting passwords, more compatible with new changes, less kludgy code as well
- Fixed longstanding warning caused by a typo in an earlier version of the code (oops)
Differences between 1.1.3 and 1.1.2[edit | edit source]
- Extra error handling plus compatibility fixes for mediawiki version 1.6.8 (hook patches added)
Differences between 1.1.2 and 1.1.1[edit | edit source]
- Compatibilty fixes for 1.9
Differences between 1.1.1 and 1.1[edit | edit source]
- Preserve compatibility with 1.0 configuration files for $shib_LoginHint
Differences between 1.1 and 1.0[edit | edit source]
- Extra configuration options
- shib_Https (whether the AssertionConsumerService url is at https, default is false)
- shib_AssertionConsumerServiceURL (defaults to /Shibboleth.sso)
- Sites that are accessed via https are redirected there after logging in using lazy authentication
- Login Form Cleanup
- New Contributor: Steven Langenaken
Acknowledgements[edit | edit source]
These folks have helped out by reporting issues and breakages or testing early code releases to help eliminate issues and breakages. Without them this software would have more bugs.
Bug and other-thing Reporters[edit | edit source]
- Martin Kos - Reported breakage in 1.1.4
- Eric Jiang - Reported breakage in 1.1.1
- Bajnokk - Reported breakage in 1.2.1
- Dhess - Provided fix for setup_session error in 1.2.2
- Barry Johnson - Provided details for shib 2.0
Testers[edit | edit source]
- Joe Pomianek - UCSD