diff --git a/modules/negotiate/dictionaries/negotiate.definition.json b/modules/negotiate/dictionaries/negotiate.definition.json new file mode 100644 index 0000000000000000000000000000000000000000..aed1d4aa98a28b7f41fdf880edc6e9473e198196 --- /dev/null +++ b/modules/negotiate/dictionaries/negotiate.definition.json @@ -0,0 +1,17 @@ +{ + "disable_title": { + "en": "Turned off ability to use automatic login for this browser" + }, + "disable_info_pre": { + "en": "<p>By accessing this page, you have turned off the ability to use automatic login for this browser.<\/p><p>To turn it on again, please visit <a href='URL'>this page<\/a>.<\/p>" + }, + "enable_title": { + "en": "Turned on the ability to use automatic login for this browser" + }, + "enable_info_pre": { + "en": "<p>By accessing this page, you have turned on the ability to use automatic login for this browser.<\/p><p>To turn it off again, please visit <a href='URL'>this page<\/a>.<\/p>" + }, + "info_post": { + "en": "<h3>What is automatic login<\/h3><p>Automatic login allows you to log in without being asked for username and password:<ul><li>if the computer is in the Kerberos domain<\/li><li>using a supported browser (IE and some others depending on installation options)<\/li><li>on a supported operating system (Windows, Mac)<\/li><\/ul><\/p>" + } +} diff --git a/modules/negotiate/dictionaries/negotiate.translation.json b/modules/negotiate/dictionaries/negotiate.translation.json new file mode 100644 index 0000000000000000000000000000000000000000..01a7323aa94e0a4b6208713d6afa7ea7796fbc7f --- /dev/null +++ b/modules/negotiate/dictionaries/negotiate.translation.json @@ -0,0 +1,22 @@ +{ + "disable_title": { + "no": "Skrudd av mulighet for automatisk innlogging for denne nettleseren", + "nn": "Skrudd av moglegheit for automatisk innlogging for denne nettlesaren" + }, + "disable_info_pre": { + "no": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd av muligheten for automatisk innlogging for denne nettleseren.<\/p><p>For \u00e5 skru p\u00e5 igjen muligheten, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>", + "nn": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd av moglegheit for automatisk innlogging for denne nettlesaren.<\/p><p>For \u00e5 skru p\u00e5 igjen moglegheita, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>" + }, + "enable_title": { + "no": "Skrudd p\u00e5 mulighet for automatisk innlogging for denne nettleseren", + "nn": "Skrudd p\u00e5 moglegheit for automatisk innlogging for denne nettlesaren" + }, + "enable_info_pre": { + "no": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd p\u00e5 muligheten for automatisk innlogging for denne nettleseren.<\/p><p>For \u00e5 skru av igjen muligheten, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>", + "nn": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd p\u00e5 moglegheit for automatisk innlogging for denne nettlesaren.<\/p><p>For \u00e5 skru av igjen moglegheita, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>" + }, + "info_post": { + "no": "<h3>Hva er automatisk innlogging<\/h3><\/p><p>Automatisk innlogging gj\u00f8r at man kan logge inn uten \u00e5 bli bedt om brukernavn og passord i noen tilfeller:<ul><li>hvis maskinen er i Kerberos domenet<\/li><li>og hvis man bruker en st\u00f8ttet nettleser (IE og noen flere avhengig av oppsett)<\/li><li>p\u00e5 et st\u00f8ttet operativsystem (Windows, Mac)<\/li><\/ul><\/p>", + "nn": "<h3>Kva er automatisk innlogging<\/h3><\/p><p>Automatisk innlogging gj\u00f8r at man kan logge inn uten \u00e5 bli bedt om brukernavn og passord i noen tilfeller:<ul><li>hvis maskinen er i Kerberos domenet<\/li><li>og hvis man bruker en st\u00f8ttet nettleser (IE og noen flere avhengig av oppsett)<\/li><li>p\u00e5 et st\u00f8ttet operativsystem (Windows, Mac)<\/li><\/ul><\/p>" + } +} diff --git a/modules/negotiate/docs/negotiate.txt b/modules/negotiate/docs/negotiate.txt new file mode 100644 index 0000000000000000000000000000000000000000..62ae6f52c3e4736120940f5c244611a09972e2e5 --- /dev/null +++ b/modules/negotiate/docs/negotiate.txt @@ -0,0 +1,279 @@ +Negotiate module +================ + +The Negotiate module implements Microsofts Kerberos SPNEGO mechanism. +It is intended to only support Kerberos and not NTLM which RFC4559 +implements. + +`negotiate:Negotiate` +: Authenticates users via HTTP authentication + +`negotiate:Negotiate` +--------------------- + +Negotiate implements the following mechanics: + + * Initiate HTTP_AUTHN with the client + * Authorize user against a LDAP directory + * Collect metadata from LDAP directory + * Fall back to other SimpleSamlPhp module for any client/user that + fails to authenticate in the Negotiate module + * Check only clients from a certain subnet + * Supports enabling/disabling a client + +In effect this module aims to extend the Microsoft AD SSO session to +the SAML IdP. (Or any other Kerberos domain) It doesn't work like this +of course but for the user the client is automatically authenticated +when an SP sends the client to the IdP. In reality Negotiate +authenticates the user via SPNEGO and issues a separate SAML session. +The Kerberos session against the Authentication Server is completely +separate from the SAML session with the IdP. The only time the +Kerberos session affects the SAML session is at authN at the IdP. + +The module is meant to supplement existing auth modules and not +replace them. Users do not always log in on the IdP from a machine in +the Windows domain (or another Kerberos domain) and from their own +domain accounts. A fallback mechanism must be supplemented. + +The Kerberos TGS can be issued for a wide variety of accounts so an +authoriation backend via LDAP is needed. If the search, with filters, +fails, the fallback in invoked. This to prevent kiosk accounts and the +likes to get faulty SAML sessions. + +The subnet is required to prevent excess attempts to authenticate via +Kerberos for clients that always will fail. Worst case scenario the +browser will prompt the user for u/p in a popup box that will always +fail. Only when the user clicks cancel the proper login process will +continue. This is handled through the body of the 401 message the +client recieves with the Negotiate request. In the body a URL to the +fallback mechanism is supplied and Javascript is used to redirect the +client. + +All configuration is handled in authsources.php: + + 'weblogin' => array( + 'negotiate:Negotiate', + 'keytab' => '/path/to/keytab-file', + 'fallback' => 'ldap', + 'hostname' => 'ldap.example.com', + 'base' => 'cn=people,dc=example,dc=com', + 'adminUser' => 'cn=idp-fallback,cn=services,dc=example,dc=com', + 'adminPassword' => 'VerySecretPassphraseHush' + ), + 'ldap' => array( + 'ldap:LDAP', + 'hostname' => 'ldap.example.com', + 'enable_tls' => TRUE, + 'dnpattern' => 'uid=%username%,cn=people,dc=example,dc=com', + 'search.enable' => FALSE + ), + + + +`php_krb5` +++++++++++ + +The processing involving the actual Kerberos ticket handling is done +by php_krb5. The package is not yet labeled stable but has worked well +during testing. + +NOTE! php_krb5 hardcodes the service name in the keytab file to 'HTTP' +as of php_krb5-1.0rc2. To change this you need to edit the module code. +Be wary of how much space is allocated to the string in +negotiate_auth.c:101. + +Depending on you apache config you may need a rewrite rule to allow +php_krb5 to read the HTTP_AUTHORIZATION header: + + RewriteEngine on + RewriteCond %{HTTP:Authorization} !^$ + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] + + +Test the Kerberos setup with the following script: + + <?php + if(!extension_loaded('krb5')) { + die('KRB5 Extension not installed'); + } + + if(!empty($_SERVER['HTTP_AUTHORIZATION'])) { + list($mech, $data) = explode(' ', $_SERVER['HTTP_AUTHORIZATION']); + if(strtolower($mech) == 'basic') { + echo "Client sent basic"; + die('Unsupported request'); + } else if(strtolower($mech) != 'negotiate') { + echo "Couldn't find negotiate"; + die('Unsupported request'); + } + $auth = new KRB5NegotiateAuth('/path/to/keytab'); + $reply = ''; + if($reply = $auth->doAuthentication()) { + header('HTTP/1.1 200 Success'); + echo 'Success - authenticated as ' . $auth->getAuthenticatedUser() . '<br>'; + } else { + echo 'Failed to authN.'; + die(); + } + } else { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Negotiate',false); + echo 'Not authenticated. No HTTP_AUTHORIZATION available.'; + echo 'Check headers sent by the browser and verify that '; + echo 'apache passes them to PHP'; + } + ?> + + + +`LDAP` +++++++ + +LDAP is used to verify the user due to the lack of metadata in +Kerberos. A domain can contain lots of kiosk users, non-personal +accounts and the likes. The LDAP lookup will authorize and fetch +attributes as defined by SimpleSamlPhp metadata. + +'hostname', 'enable_tls', 'debugLDAP', 'timeout' and 'base' are +self-explanatory. Read the documentation of the LDAP auth module for +more information. 'attr' is the attribute that will be used to look up +user objects in the directory after extracting it from the Kerberos +session. Default is 'uid'. + +For LDAP directories with restricted access to objects or attributes +Negotiate implements 'adminUser' and 'adminPassword'. adminUser must +be a DN to an object with access to search for all relevant user +objects and to look up attributes needed by the SP. + + +`Subnet filtering` +++++++++++++++++++ + +Subnet is meant to filter which clients you subject to the +WWW-Authenticate request. + +Syntax is: + + 'subnet' => array('127.0.0.0/16','192.168.0.0/16'), + +Browsers, especially IE, behave erratically when they encounter a +WWW-Authenticate from the webserver. Included in RFC4559 Negotiate is +NTLM authentication which IE seems prone to fall back to under various +conditions. This triggers a popup login box which defeats the whole +purpose of this module. + +TBD: Replace or supplement with LDAP lookups in the domain. Machines +currently in the domain should be the only ones that are promted with +WWW-Authenticate: Negotiate. + + +`Enabling/disabling Negotiate from a web browser` ++++++++++++++++++++++++++++++++++++++++++++++++++ + +Included in Negotiate are semi-static web pages for enabling and +disabling Negotiate for any given client. The pages simple set/deletes +a cookie that Negotiate will look for when a client attempts AuthN. +The help text in the JSON files should be locally overwritten to fully +explain which clients are accepted by Negotiate. + + +`Logout/Login loop and reauthenticating` +++++++++++++++++++++++++++++++++++++++++ + +Due to the automatic AuthN of certain clients and how SPs will +automatically redirect clients to the IdP when clients try to access +restricted content, a session variable has been put into Negotiate. This +variable makes sure Negotiate doesn't reautenticate a recently logged +out user. The consequence of this is that the user will be presented +with the login mechanism of the fallback module specified in Negotiate +config. + +SimpleSamlPhp offers no decent way of adding hooks or piggyback this +information to the fallback module. In future releases one might add a +box of information to the user explaining what's happening. + +One can add this bit of code to the template in the fallback AuthN +module: + +// This should be placed in your www script +$nego_session = FALSE; +$nego_perm = FALSE; +$nego_retry = NULL; +if (array_key_exists('negotiate:authId', $state)) { + $nego = SimpleSAML_Auth_Source::getById($state['negotiate:authId']); + $mask = $nego->checkMask(); + $disabled = $nego->spDisabledInMetadata($spMetadata); + $session_disabled = $session->getData('negotiate:disable', 'session'); + if ($mask and !$disabled) { + if(array_key_exists('NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT', $_COOKIE) && + $_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT'] == 'True') { + $nego_perm = TRUE; + } elseif ($session_disabled) { + $retryState = SimpleSAML_Auth_State::cloneState($state); + unset($retryState[SimpleSAML_Auth_State::ID]); + $nego_retry = SimpleSAML_Auth_State::saveState($retryState, 'sspmod_negotiate_Auth_Source_Negotiate.StageId'); + $nego_session = TRUE; + } + } +} + +// This should reside in your template +if($this->data['nego']['disable_perm']) { + echo '<span id="login-extra-info-uio.no" class="login-extra-info">' + . '<span class="login-extra-info-divider"></span>' + . $this->t('{feide:login:login_uio_negotiate_disabled_permanent_info}') + . '</span>'; +} elseif($this->data['nego']['disable_session']) { + echo '<span id="login-extra-info-uio.no" class="login-extra-info">' + . '<span class="login-extra-info-divider"></span>' + . $this->t('{feide:login:login_uio_negotiate_disabled_session_info}') + . '<br><a href="'.SimpleSAML_Module::getModuleURL('negotiate/retry.php', array('AuthState' => $this->data['nego']['retry_id'])).'">' + . $this->t('{feide:login:login_uio_negotiate_disabled_session_info_link}') + . '</a>' + . '</span>'; +} + +The above may or may not work right out of the box for you but it is +the gist of it. By looking at the state variable, cookie and checking +for filters and the likes, only clients that are subjected to +Negotiate should get the help text. + +Note that with Negotiate there is also a small script to allow the +user to re-authenticate with Negotiate after being sent to the +fallback mechanism due to the session cookie. In the example above you +can see the construction of the URL. The cloning of the current state +is necessary for retry.php to load a state without triggering a +security check in SSP's state handling library. If you omit this and +pass on the original state you will see a warning in the log like +this: + + Sep 27 13:47:36 simplesamlphp WARNING [b99e6131ee] Wrong stage in state. Was 'foo', should be 'sspmod_negotiate_Auth_Source_Negotiate.StageId'. + +It will work as loadState will take controll and call +Negotiate->authenticate() but remaining code in retry.php will be +discarded. Other side-effects may occur. + + +`Clients` ++++++++++ + +* Internet Explorer + +YMMV but generally you need to have your IdP defined in "Internet +Options" -> "Security" -> "Local intranet" -> "Sites" -> "Advanced". +You also need "Internet Options" -> "Advanced" -> "Security" -> Enable +Integrated Windows Authentication" enabled. + +* Firefox + +Open "about:config". Locate "network.auth.use-sspi" and verify that +this is true (on a Windows machine). Next locate +"network.negotiate-auth.trusted-uris" and insert your IdP. + +* Safari + +TODO + +* Chrome + +TODO diff --git a/modules/negotiate/lib/Auth/Source/Negotiate.php b/modules/negotiate/lib/Auth/Source/Negotiate.php new file mode 100644 index 0000000000000000000000000000000000000000..d2f49792b5c7d9858c4047ab6259f3ed48d66914 --- /dev/null +++ b/modules/negotiate/lib/Auth/Source/Negotiate.php @@ -0,0 +1,321 @@ +<?php + +/** + * The Negotiate module. Allows for password-less, secure login by + * Kerberos and Negotiate. + * + * @author Mathias Meisfjordskar, University of Oslo. + * <mathias.meisfjordskar@usit.uio.no> + * @package simpleSAMLphp + * @version $Id$ + */ + +class sspmod_negotiate_Auth_Source_Negotiate extends SimpleSAML_Auth_Source { + + // Constants used in the module + const STAGEID = 'sspmod_negotiate_Auth_Source_Negotiate.StageId'; + + private $config; + protected $ldap = NULL; + + /** + * Constructor for this authentication source. + * + * @param array $info Information about this authentication source. + * @param array $config Configuration. + */ + public function __construct($info, $config) { + assert('is_array($info)'); + assert('is_array($config)'); + + if(!extension_loaded('krb5')) + throw new Exception('KRB5 Extension not installed'); + + // Call the parent constructor first, as required by the interface. + parent::__construct($info, $config); + + $config = SimpleSAML_Configuration::loadFromArray($config);; + + $this->backend = $config->getString('fallback'); + $this->hostname = $config->getString('hostname'); + $this->enableTLS = $config->getBoolean('enable_tls', FALSE); + $this->debugLDAP = $config->getBoolean('debugLDAP', FALSE); + $this->timeout = $config->getValue('timeout', 30); + $this->keytab = $config->getString('keytab'); + $this->base = $config->getString('base'); + $this->attr = $config->getString('attr', 'uid'); + $this->subnet = $config->getArray('subnet', NULL); + $this->admin_user = $config->getString('adminUser', NULL); + $this->admin_pw = $config->getString('adminPassword', NULL); + + } + + /** + * The inner workings of the module. + * + * Checks to see if client is in the defined subnets (if + * defined in config). Sends the client a 401 Negotiate and + * responds to the result. If the client fails to provide a + * proper Kerberos ticket, the login process is handed over to + * the 'fallback' module defined in the config. + * + * LDAP is used as a user metadata source. + * + * @param array &$state Information about the current authentication. + */ + public function authenticate(&$state) { + assert('is_array($state)'); + + // Set the default backend to config + $state['LogoutState'] = array( + 'negotiate:backend' => $this->backend, + ); + $state['negotiate:authId'] = $this->authId; + + + // Check for disabled SPs. The disable flag is store in the SP + // metadata. + if (array_key_exists('SPMetadata', $state) and $this->spDisabledInMetadata($state['SPMetadata'])) + $this->fallBack($state); + // Go straight to fallback if Negotiate is disabled or if you are + // sent back to the IdP directly from the SP after having logged out + $session = SimpleSAML_Session::getInstance(); + $disabled = $session->getData('negotiate:disable', 'session'); + + if ($disabled or + (!empty($_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT']) and + $_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT'] == 'True')) { + SimpleSAML_Logger::debug('Negotiate - session disabled. falling back'); + $this->fallBack($state); + // Never executed + assert('FALSE'); + } + $mask = $this->checkMask(); + if (!$mask) { + $this->fallBack($state); + // Never executed + assert('FALSE'); + } + + SimpleSAML_Logger::debug('Negotiate - authenticate(): looking for Negotate'); + if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { + SimpleSAML_Logger::debug('Negotiate - authenticate(): Negotate found'); + $this->ldap = new SimpleSAML_Auth_LDAP($this->hostname, $this->enableTLS, $this->debugLDAP, $this->timeout); + + list($mech, $data) = explode(' ', $_SERVER['HTTP_AUTHORIZATION'],2); + if(strtolower($mech) == 'basic') + SimpleSAML_Logger::debug('Negotiate - authenticate(): Basic found. Skipping.'); + else if(strtolower($mech) != 'negotiate') + SimpleSAML_Logger::debug('Negotiate - authenticate(): No "Negotiate" found. Skipping.'); + + $auth = new KRB5NegotiateAuth($this->keytab); + // Atempt Kerberos authentication + try { + $reply = $auth->doAuthentication(); + } catch (Exception $e) { + SimpleSAML_Logger::error('Negotiate - authenticate(): doAuthentication() exception: '. $e->getMessage()); + $reply = NULL; + } + + if($reply) { + // Success. Krb TGS recieved. + $user = $auth->getAuthenticatedUser(); + SimpleSAML_Logger::info('Negotiate - authenticate(): '. $user . ' authenticated.'); + $lookup = $this->lookupUserData($user); + if ($lookup) { + $state['Attributes'] = $lookup; + // Override the backend so logout will know what to look for. + $state['LogoutState'] = array( + 'negotiate:backend' => NULL, + ); + SimpleSAML_Logger::info('Negotiate - authenticate(): '. $user . ' authorized.'); + SimpleSAML_Auth_Source::completeAuth($state); + // Never reached. + assert('FALSE'); + } + } else { + // Some error in the recieved ticket. Expired? + SimpleSAML_Logger::info('Negotiate - authenticate(): Kerberos authN failed. Skipping.'); + } + } else { + // No auth token. Send it. + SimpleSAML_Logger::debug('Negotiate - authenticate(): Sending Negotiate.'); + // Save the $state array, so that we can restore if after a redirect + SimpleSAML_Logger::debug('Negotiate - fallback: '.$state['LogoutState']['negotiate:backend']); + $id = SimpleSAML_Auth_State::saveState($state, self::STAGEID); + $params = array('AuthState' => $id); + + $this->sendNegotiate($params); + exit; + } + + SimpleSAML_Logger::info('Negotiate - authenticate(): Client failed Negotiate. Falling back'); + $this->fallBack($state); + /* The previous function never returns, so this code is never + executed */ + assert('FALSE'); + } + + public function spDisabledInMetadata($spMetadata) { + if (array_key_exists('negotiate:disable', $spMetadata)) { + if ($spMetadata['negotiate:disable'] == TRUE) { + SimpleSAML_Logger::debug('Negotiate - SP disabled. falling back'); + return True; + // Never executed + assert('FALSE'); + } else { + SimpleSAML_Logger::debug('Negotiate - SP disable flag found but set to FALSE'); + } + } else { + SimpleSAML_Logger::debug('Negotiate - SP disable flag not found'); + } + return False; + } + + /** + * checkMask() looks up the subnet config option and verifies + * that the client is within that range. + * + * Will return TRUE if no subnet option is configured. + * + * @return boolean + */ + public function checkMask() { + // No subnet means all clients are accepted. + if ($this->subnet === NULL) + return TRUE; + $ip = $_SERVER['REMOTE_ADDR']; + foreach ($this->subnet as $cidr) { + $ret = SimpleSAML_Utilities::ipCIDRcheck($cidr); + if ($ret) { + SimpleSAML_Logger::debug('Negotiate: Client "'.$ip.'" matched subnet.'); + return TRUE; + } + } + SimpleSAML_Logger::debug('Negotiate: Client "'.$ip.'" did not match subnet.'); + return FALSE; + } + + /** + * Send the actual headers and body of the 401. Embedded in + * the body is a post that is triggered by JS if the client + * wants to show the 401 message. + * + * @param array $params additional parameters to the URL in + * the URL in the body + */ + protected function sendNegotiate($params) { + $url = SimpleSAML_Module::getModuleURL('negotiate/backend.php', $params); + + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Negotiate',false); + echo ' +<html> +<head> +<script type="text/javascript">window.location = '.json_encode(htmlspecialchars($url)).'</script> +<title>Redirect to login</title> +</head> +<body> +Your browser seems to have Javascript disabled. Please +click <a href="'.htmlspecialchars($url).'">here</a>. +</body> +</html> '; + + } + + /** + * Passes control of the login process to a different module. + * + * @param string $state Information about the current authentication. + */ + public static function fallBack(&$state) { + $authId = $state['LogoutState']['negotiate:backend']; + + if ($authId === NULL) { + $msg = "This code should never be reached."; + throw new SimpleSAML_Error_AuthSource($msg); + } + $source = SimpleSAML_Auth_Source::getById($authId); + + try { + $source->authenticate($state); + } catch (SimpleSAML_Error_Exception $e) { + SimpleSAML_Auth_State::throwException($state, $e); + } catch (Exception $e) { + $e = new SimpleSAML_Error_UnserializableException($e); + SimpleSAML_Auth_State::throwException($state, $e); + } + // fallBack never returns after loginCompleted() + SimpleSAML_Logger::debug('Negotiate: backend returned'); + self::loginCompleted($state); + } + + /** + * Strips away the realm of the Kerberos identifier, looks up + * what attributes to fetch from SP metadata and searches the + * directory. + * + * @param string $user The Kerberos user identifier + * @return string The DN to the user or NULL if not found + */ + protected function lookupUserData($user) { + // Kerberos usernames include realm. Strip that away. + $pos = strpos($user, '@'); + if ($pos === false) + return NULL; + $uid = substr($user, 0, $pos); + + $this->adminBind(); + try { + $dn = $this->ldap->searchfordn($this->base, $this->attr, $uid); + return $this->ldap->getAttributes($dn); + } catch (SimpleSAML_Error_Exception $e) { + SimpleSAML_Logger::debug('Negotiate - ldap lookup failed: '. $e); + return NULL; + } + } + + /** + * Elevates the LDAP connection to allow restricted lookups if + * so configured. Does nothing if not. + */ + protected function adminBind() { + if ($this->admin_user === NULL) { + // No admin user. + return; + } + SimpleSAML_Logger::debug('Negotiate - authenticate(): Binding as system user ' . var_export($this->admin_user, TRUE)); + + if(!$this->ldap->bind($this->admin_user, $this->admin_pw)){ + $msg = 'Unable to authenticate system user (LDAP_INVALID_CREDENTIALS) ' . var_export($this->admin_user, TRUE); + SimpleSAML_Logger::error('Negotiate - authenticate(): ' . $msg); + throw new SimpleSAML_Error_AuthSource($msg); + } + } + + /** + * Log out from this authentication source. + * + * This method either logs the user out from Negotiate or passes the + * logout call to the fallback module. + * + * @param array &$state Information about the current logout operation. + */ + public function logout(&$state) { + assert('is_array($state)'); + /* Get the source that was used to authenticate */ + $authId = $state['negotiate:backend']; + SimpleSAML_Logger::debug('Negotiate - logout has the following authId: "'.$authId.'"'); + + if ($authId === NULL) { + $session = SimpleSAML_Session::getInstance(); + $session->setData('negotiate:disable', 'session', TRUE, 24*60*60); + parent::logout($state); + } else { + $source = SimpleSAML_Auth_Source::getById($authId); + $source->logout($state); + } + } + +} + diff --git a/modules/negotiate/templates/disable.php b/modules/negotiate/templates/disable.php new file mode 100644 index 0000000000000000000000000000000000000000..acb8480ae764f6861687bae83818bd6c00e05c11 --- /dev/null +++ b/modules/negotiate/templates/disable.php @@ -0,0 +1,21 @@ +<?php + +/** + * + * + * @author Mathias Meisfjordskar, University of Oslo. + * <mathias.meisfjordskar@usit.uio.no> + * @package simpleSAMLphp + * @version $Id$ + */ +$this->includeAtTemplateBase('includes/header.php'); +?> +<h1><?php echo $this->t('{negotiate:negotiate:disable_title}'); ?></h1> +<?php +$url = SimpleSAML_Module::getModuleURL('negotiate/enable.php'); +?> +<?php echo $this->t('{negotiate:negotiate:disable_info_pre}', array('URL' => htmlspecialchars($url))); ?> + +<?php echo $this->t('{negotiate:negotiate:info_post}'); ?> + +<?php $this->includeAtTemplateBase('includes/footer.php'); ?> diff --git a/modules/negotiate/templates/enable.php b/modules/negotiate/templates/enable.php new file mode 100644 index 0000000000000000000000000000000000000000..05caeb996e47c2a8a15026495d3c982d56f7ea3d --- /dev/null +++ b/modules/negotiate/templates/enable.php @@ -0,0 +1,22 @@ +<?php + +/** + * + * + * @author Mathias Meisfjordskar, University of Oslo. + * <mathias.meisfjordskar@usit.uio.no> + * @package simpleSAMLphp + * @version $Id$ + */ +$this->includeAtTemplateBase('includes/header.php'); +?> +<h1><?php echo $this->t('{negotiate:negotiate:enable_title}'); ?></h1> + +<?php +$url = SimpleSAML_Module::getModuleURL('negotiate/disable.php'); +?> +<?php echo $this->t('{negotiate:negotiate:enable_info_pre}', array('URL' => htmlspecialchars($url))); ?> + +<?php echo $this->t('{negotiate:negotiate:info_post}'); ?> + +<?php $this->includeAtTemplateBase('includes/footer.php'); ?> diff --git a/modules/negotiate/www/backend.php b/modules/negotiate/www/backend.php new file mode 100644 index 0000000000000000000000000000000000000000..f9d3622296bcf95b3ac7fcd170ddc782a8c9e1b3 --- /dev/null +++ b/modules/negotiate/www/backend.php @@ -0,0 +1,18 @@ +<?php + +/** + * Provide a URL for the module to statically link to. + * + * @author Mathias Meisfjordskar, University of Oslo. + * <mathias.meisfjordskar@usit.uio.no> + * @package simpleSAMLphp + * @version $Id$ + */ + +$authStateId = $_REQUEST['AuthState']; +$state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_negotiate_Auth_Source_Negotiate::STAGEID); +SimpleSAML_Logger::debug('backend - fallback: '.$state['LogoutState']['negotiate:backend']); + +sspmod_negotiate_Auth_Source_Negotiate::fallBack($state); + +exit; diff --git a/modules/negotiate/www/disable.php b/modules/negotiate/www/disable.php new file mode 100644 index 0000000000000000000000000000000000000000..021fb7bc400313dc1478def7f58c622895d04fd5 --- /dev/null +++ b/modules/negotiate/www/disable.php @@ -0,0 +1,17 @@ +<?php + +/** + * + * + * @author Mathias Meisfjordskar, University of Oslo. + * <mathias.meisfjordskar@usit.uio.no> + * @package simpleSAMLphp + * @version $Id$ + */ + +$globalConfig = SimpleSAML_Configuration::getInstance(); +setcookie('NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT', 'True', mktime(0,0,0,1,1,2038), '/', SimpleSAML_Utilities::getSelfHost(), FALSE, TRUE); +$session = SimpleSAML_Session::getInstance(); +$session->setData('negotiate:disable', 'session', FALSE, 24*60*60); +$t = new SimpleSAML_XHTML_Template($globalConfig, 'negotiate:disable.php'); +$t->show(); diff --git a/modules/negotiate/www/enable.php b/modules/negotiate/www/enable.php new file mode 100644 index 0000000000000000000000000000000000000000..340b9dcd00b9e8118b95c6d506a323b9527eb8f5 --- /dev/null +++ b/modules/negotiate/www/enable.php @@ -0,0 +1,17 @@ +<?php + +/** + * + * + * @author Mathias Meisfjordskar, University of Oslo. + * <mathias.meisfjordskar@usit.uio.no> + * @package simpleSAMLphp + * @version $Id$ + */ + +$globalConfig = SimpleSAML_Configuration::getInstance(); +setcookie('NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT', 'False', time() - 3600, '/', SimpleSAML_Utilities::getSelfHost(), FALSE, TRUE); +$session = SimpleSAML_Session::getInstance(); +$session->setData('negotiate:disable', 'session', FALSE, 24*60*60); +$t = new SimpleSAML_XHTML_Template($globalConfig, 'negotiate:enable.php'); +$t->show(); diff --git a/modules/negotiate/www/retry.php b/modules/negotiate/www/retry.php new file mode 100644 index 0000000000000000000000000000000000000000..2d12eab97ebe92777c8108b067a8aed5e1472a4b --- /dev/null +++ b/modules/negotiate/www/retry.php @@ -0,0 +1,32 @@ +<?php + +/** + * + * + * @author Mathias Meisfjordskar, University of Oslo. + * <mathias.meisfjordskar@usit.uio.no> + * @package simpleSAMLphp + * @version $Id$ + */ + +$authStateId = $_REQUEST['AuthState']; +$state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_negotiate_Auth_Source_Negotiate::STAGEID); + +$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); +$idpid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted', 'metaindex'); +$idpmeta = $metadata->getMetaData($idpid, 'saml20-idp-hosted'); + +if (isset($idpmeta['auth'])) { + $source = SimpleSAML_Auth_Source::getById($idpmeta['auth']); + if ($source === NULL) + throw new SimpleSAML_Error_BadRequest('Invalid AuthId "' . $idpmeta['auth'] . '" - not found.'); + + $session = SimpleSAML_Session::getInstance(); + $session->setData('negotiate:disable', 'session', FALSE, 24*60*60); + SimpleSAML_Logger::debug('Negotiate(retry) - session enabled, retrying.'); + $source->authenticate($state); + assert('FALSE'); +} else { + SimpleSAML_Logger::error('Negotiate - retry - no "auth" parameter found in IdP metadata.'); + assert('FALSE'); +}