3 * @author Martin Dougiamas
4 * @author Lukas Haemmerle
5 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
6 * @package moodle multiauth
8 * Authentication Plugin: Shibboleth Authentication
10 * Authentication using Shibboleth.
12 * Distributed under GPL (c)Markus Hagman 2004-2006
14 * 10.2004 SHIBBOLETH Authentication functions v.0.1
15 * 05.2005 Various extensions and fixes by Lukas Haemmerle
16 * 10.2005 Added better error messags
17 * 05.2006 Added better handling of mutli-valued attributes
18 * 2006-08-28 File created, code imported from lib.php
19 * 2006-10-27 Upstream 1.7 changes merged in, added above credits from lib.php :-)
20 * 2007-03-09 Fixed authentication but may need some other changes
21 * 2007-10-03 Removed requirement for email address, surname and given name on request of Markus Hagman
22 * 2008-01-21 Added WAYF functionality
26 if (!defined('MOODLE_INTERNAL')) {
27 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
30 require_once($CFG->libdir
.'/authlib.php');
33 * Shibboleth authentication plugin.
35 class auth_plugin_shibboleth
extends auth_plugin_base
{
40 function auth_plugin_shibboleth() {
41 $this->authtype
= 'shibboleth';
42 $this->config
= get_config('auth/shibboleth');
46 * Returns true if the username and password work and false if they are
47 * wrong or don't exist.
49 * @param string $username The username (with system magic quotes)
50 * @param string $password The password (with system magic quotes)
51 * @return bool Authentication success or failure.
53 function user_login($username, $password) {
55 // If we are in the shibboleth directory then we trust the server var
56 if (!empty($_SERVER[$this->config
->user_attribute
])) {
57 return (strtolower($_SERVER[$this->config
->user_attribute
]) == strtolower($username));
59 // If we are not, the user has used the manual login and the login name is
60 // unknown, so we return false.
68 * Returns the user information for 'external' users. In this case the
69 * attributes provided by Shibboleth
71 * @return array $result Associative array of user data
73 function get_userinfo($username) {
74 // reads user information from shibboleth attributes and return it in array()
77 // Check whether we have got all the essential attributes
78 if ( empty($_SERVER[$this->config
->user_attribute
]) ) {
79 print_error( 'shib_not_all_attributes_error', 'auth' , '', "'".$this->config
->user_attribute
."' ('".$_SERVER[$this->config
->user_attribute
]."'), '".$this->config
->field_map_firstname
."' ('".$_SERVER[$this->config
->field_map_firstname
]."'), '".$this->config
->field_map_lastname
."' ('".$_SERVER[$this->config
->field_map_lastname
]."') and '".$this->config
->field_map_email
."' ('".$_SERVER[$this->config
->field_map_email
]."')");
82 $attrmap = $this->get_attributes();
85 $search_attribs = array();
87 foreach ($attrmap as $key=>$value) {
88 // Check if attribute is present
89 if (!isset($_SERVER[$value])){
94 // Make usename lowercase
95 if ($key == 'username'){
96 $result[$key] = strtolower($this->get_first_string($_SERVER[$value]));
98 $result[$key] = $this->get_first_string($_SERVER[$value]);
102 // Provide an API to modify the information to fit the Moodle internal
103 // data representation
105 $this->config
->convert_data
106 && $this->config
->convert_data
!= ''
107 && is_readable($this->config
->convert_data
)
110 // Include a custom file outside the Moodle dir to
111 // modify the variable $moodleattributes
112 include($this->config
->convert_data
);
119 * Returns array containg attribute mappings between Moodle and Shibboleth.
123 function get_attributes() {
124 $configarray = (array) $this->config
;
126 $moodleattributes = array();
127 foreach ($this->userfields
as $field) {
128 if (isset($configarray["field_map_$field"])) {
129 $moodleattributes[$field] = $configarray["field_map_$field"];
132 $moodleattributes['username'] = $configarray["user_attribute"];
134 return $moodleattributes;
138 * Returns true if this authentication plugin is 'internal'.
142 function is_internal() {
147 * Returns true if this authentication plugin can change the user's
152 function can_change_password() {
157 * Hook for login page
160 function loginpage_hook() {
161 global $SESSION, $CFG;
163 // Prevent username from being shown on login page after logout
164 $CFG->nolastloggedin
= true;
170 * Prints a form for configuring this authentication plugin.
172 * This function is called from admin/auth.php, and outputs a full page with
173 * a form for configuring this plugin.
175 * @param array $page An object containing all the data for this page.
177 function config_form($config, $err, $user_fields) {
178 include "config.html";
182 * Processes and stores configuration data for this authentication plugin.
185 * @param object $config Configuration object
187 function process_config($config) {
190 // set to defaults if undefined
191 if (!isset($config->auth_instructions
) or empty($config->user_attribute
)) {
192 $config->auth_instructions
= get_string('shibboleth_instructions', 'auth', $CFG->wwwroot
.'/auth/shibboleth/index.php');
194 if (!isset ($config->user_attribute
)) {
195 $config->user_attribute
= '';
197 if (!isset ($config->convert_data
)) {
198 $config->convert_data
= '';
201 if (!isset($config->changepasswordurl
)) {
202 $config->changepasswordurl
= '';
205 if (!isset($config->login_name
)) {
206 $config->login_name
= 'Shibboleth Login';
210 if (isset($config->organization_selection
) && !empty($config->organization_selection
) && isset($config->alt_login
) && $config->alt_login
== 'on') {
211 $idp_list = get_idp_list($config->organization_selection
);
212 if (count($idp_list) < 1){
215 $config->organization_selection
= '';
216 foreach ($idp_list as $idp => $value){
217 $config->organization_selection
.= $idp.', '.$value[0].', '.$value[1]."\n";
223 set_config('user_attribute', $config->user_attribute
, 'auth/shibboleth');
225 if (isset($config->organization_selection
) && !empty($config->organization_selection
)) {
226 set_config('organization_selection', $config->organization_selection
, 'auth/shibboleth');
228 set_config('login_name', $config->login_name
, 'auth/shibboleth');
229 set_config('convert_data', $config->convert_data
, 'auth/shibboleth');
230 set_config('auth_instructions', $config->auth_instructions
, 'auth/shibboleth');
231 set_config('changepasswordurl', $config->changepasswordurl
, 'auth/shibboleth');
233 if (isset($config->alt_login
) && $config->alt_login
== 'on'){
234 set_config('alt_login', $config->alt_login
, 'auth/shibboleth');
235 set_config('alternateloginurl', $CFG->wwwroot
.'/auth/shibboleth/login.php');
237 set_config('alt_login', 'off', 'auth/shibboleth');
238 set_config('alternateloginurl', '');
239 $config->alt_login
= 'off';
242 // Check values and return false if something is wrong
243 // Patch Anyware Technologies (14/05/07)
244 if (($config->convert_data
!= '')&&(!file_exists($config->convert_data
) ||
!is_readable($config->convert_data
))){
248 // Check if there is at least one entry in the IdP list
249 if (isset($config->organization_selection
) && empty($config->organization_selection
) && isset($config->alt_login
) && $config->alt_login
== 'on'){
257 * Cleans and returns first of potential many values (multi-valued attributes)
259 * @param string $string Possibly multi-valued attribute from Shibboleth
261 function get_first_string($string) {
262 $list = split( ';', $string);
263 $clean_string = rtrim($list[0]);
265 return $clean_string;
271 * Sets the standard SAML domain cookie that is also used to preselect
272 * the right entry on the local wayf
274 * @param IdP identifiere
276 function set_saml_cookie($selectedIDP) {
277 if (isset($_COOKIE['_saml_idp']))
279 $IDPArray = generate_cookie_array($_COOKIE['_saml_idp']);
285 $IDPArray = appendCookieValue($selectedIDP, $IDPArray);
286 setcookie ('_saml_idp', generate_cookie_value($IDPArray), time() +
(100*24*3600));
290 * Prints the option elements for the select element of the drop down list
293 function print_idp_list(){
294 $config = get_config('auth/shibboleth');
296 $IdPs = get_idp_list($config->organization_selection
);
297 if (isset($_COOKIE['_saml_idp'])){
298 $idp_cookie = generate_cookie_array($_COOKIE['_saml_idp']);
300 $selectedIdP = array_pop($idp_cookie);
301 } while (!isset($IdPs[$selectedIdP]) && count($idp_cookie) > 0);
307 foreach($IdPs as $IdP => $data){
308 if ($IdP == $selectedIdP){
309 echo '<option value="'.$IdP.'" selected="selected">'.$data[0].'</option>';
311 echo '<option value="'.$IdP.'">'.$data[0].'</option>';
318 * Generate array of IdPs from Moodle Shibboleth settings
320 * @param string Text containing tuble/triple of IdP entityId, name and (optionally) session initiator
321 * @return array Identifier of IdPs and their name/session initiator
324 function get_idp_list($organization_selection) {
327 $idp_raw_list = split("\n", $organization_selection);
329 foreach ($idp_raw_list as $idp_line){
330 $idp_data = split(',', $idp_line);
331 if (isset($idp_data[2]))
333 $idp_list[trim($idp_data[0])] = array(trim($idp_data[1]),trim($idp_data[2]));
335 elseif(isset($idp_data[1]))
337 $idp_list[trim($idp_data[0])] = array(trim($idp_data[1]));
345 * Generates an array of IDPs using the cookie value
347 * @param string Value of SAML domain cookie
348 * @return array Identifiers of IdPs
350 function generate_cookie_array($value) {
352 // Decodes and splits cookie value
353 $CookieArray = split(' ', $value);
354 $CookieArray = array_map('base64_decode', $CookieArray);
360 * Generate the value that is stored in the cookie using the list of IDPs
362 * @param array IdP identifiers
363 * @return string SAML domain cookie value
365 function generate_cookie_value($CookieArray) {
367 // Merges cookie content and encodes it
368 $CookieArray = array_map('base64_encode', $CookieArray);
369 $value = implode(' ', $CookieArray);
374 * Append a value to the array of IDPs
376 * @param string IdP identifier
377 * @param array IdP identifiers
378 * @return array IdP identifiers with appended IdP
380 function appendCookieValue($value, $CookieArray) {
382 array_push($CookieArray, $value);
383 $CookieArray = array_reverse($CookieArray);
384 $CookieArray = array_unique($CookieArray);
385 $CookieArray = array_reverse($CookieArray);