diff --git a/config-templates/authsources.php b/config-templates/authsources.php index 093e9dfaa5b5330e1140f72d593418e35023cfd7..87d1834190581d8ee29dc136ecdc8df3ef5433ea 100644 --- a/config-templates/authsources.php +++ b/config-templates/authsources.php @@ -52,6 +52,12 @@ $config = array( /* 'example-userpass' => array( 'exampleauth:UserPass', + + // Give the user an option to save their username for future login attempts + // And when enabled, what should the default be, to save the username or not + //'remember.username.enabled' => FALSE, + //'remember.username.checked' => FALSE, + 'student:studentpass' => array( 'uid' => array('test'), 'eduPersonAffiliation' => array('member', 'student'), @@ -213,6 +219,11 @@ $config = array( 'example-ldap' => array( 'ldap:LDAP', + // Give the user an option to save their username for future login attempts + // And when enabled, what should the default be, to save the username or not + //'remember.username.enabled' => FALSE, + //'remember.username.checked' => FALSE, + // The hostname of the LDAP server. 'hostname' => 'ldap.example.org', @@ -278,6 +289,11 @@ $config = array( 'example-ldapmulti' => array( 'ldap:LDAPMulti', + // Give the user an option to save their username for future login attempts + // And when enabled, what should the default be, to save the username or not + //'remember.username.enabled' => FALSE, + //'remember.username.checked' => FALSE, + // The way the organization as part of the username should be handled. // Three possible values: // - 'none': No handling of the organization. Allows '@' to be part diff --git a/dictionaries/login.definition.json b/dictionaries/login.definition.json index 478f852a559a9c199d036f7405a44eed5f69ab76..1acb64e1063b3695bcebcdb3517cb3f3659119ec 100644 --- a/dictionaries/login.definition.json +++ b/dictionaries/login.definition.json @@ -55,5 +55,8 @@ }, "contact_info": { "en": "Contact information:" + }, + "remember_username": { + "en": "Remember my username" } } diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php index 0b63e92838fcd660b8dd6102b30a8bc51d474932..b7ee4dc74bdd813070a2386f4e3187a634d891e9 100644 --- a/modules/core/lib/Auth/UserPassBase.php +++ b/modules/core/lib/Auth/UserPassBase.php @@ -39,6 +39,22 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source { */ protected $loginLinks; + /** + * Storage for authsource config option remember.username.enabled + * loginuserpass.php and loginuserpassorg.php pages/templates use this option to + * present users with a checkbox to save their username for the next login request. + * @var bool + */ + protected $rememberUsernameEnabled = FALSE; + + /** + * Storage for authsource config option remember.username.checked + * loginuserpass.php and loginuserpassorg.php pages/templates use this option + * to default the remember username checkbox to checked or not. + * @var bool + */ + protected $rememberUsernameChecked = FALSE; + /** * Constructor for this authentication source. @@ -59,6 +75,16 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source { /* Call the parent constructor first, as required by the interface. */ parent::__construct($info, $config); + + // Get the remember username config options + if (isset($config['remember.username.enabled'])) { + $this->rememberUsernameEnabled = (bool) $config['remember.username.enabled']; + unset($config['remember.username.enabled']); + } + if (isset($config['remember.username.checked'])) { + $this->rememberUsernameChecked = (bool) $config['remember.username.checked']; + unset($config['remember.username.checked']); + } } @@ -79,6 +105,22 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source { return $this->loginLinks; } + /** + * Getter for the authsource config option remember.username.enabled + * @return bool + */ + public function getRememberUsernameEnabled() { + return $this->rememberUsernameEnabled; + } + + /** + * Getter for the authsource config option remember.username.checked + * @return bool + */ + public function getRememberUsernameChecked() { + return $this->rememberUsernameChecked; + } + /** * Initialize login. diff --git a/modules/core/lib/Auth/UserPassOrgBase.php b/modules/core/lib/Auth/UserPassOrgBase.php index e07eccb4341a2e93eb6e1886f57d8a5a6162848c..1c151fff84e2d81f8d3d677fbe1edf9b1fd65ef7 100644 --- a/modules/core/lib/Auth/UserPassOrgBase.php +++ b/modules/core/lib/Auth/UserPassOrgBase.php @@ -42,6 +42,22 @@ abstract class sspmod_core_Auth_UserPassOrgBase extends SimpleSAML_Auth_Source { */ private $usernameOrgMethod; + /** + * Storage for authsource config option remember.username.enabled + * loginuserpass.php and loginuserpassorg.php pages/templates use this option to + * present users with a checkbox to save their username for the next login request. + * @var bool + */ + protected $rememberUsernameEnabled = FALSE; + + /** + * Storage for authsource config option remember.username.checked + * loginuserpass.php and loginuserpassorg.php pages/templates use this option + * to default the remember username checkbox to checked or not. + * @var bool + */ + protected $rememberUsernameChecked = FALSE; + /** * Constructor for this authentication source. @@ -59,6 +75,14 @@ abstract class sspmod_core_Auth_UserPassOrgBase extends SimpleSAML_Auth_Source { /* Call the parent constructor first, as required by the interface. */ parent::__construct($info, $config); + // Get the remember username config options + if (isset($config['remember.username.enabled'])) { + $this->rememberUsernameEnabled = (bool) $config['remember.username.enabled']; + } + if (isset($config['remember.username.checked'])) { + $this->rememberUsernameChecked = (bool) $config['remember.username.checked']; + } + $this->usernameOrgMethod = 'none'; } @@ -96,6 +120,22 @@ abstract class sspmod_core_Auth_UserPassOrgBase extends SimpleSAML_Auth_Source { return $this->usernameOrgMethod; } + /** + * Getter for the authsource config option remember.username.enabled + * @return bool + */ + public function getRememberUsernameEnabled() { + return $this->rememberUsernameEnabled; + } + + /** + * Getter for the authsource config option remember.username.checked + * @return bool + */ + public function getRememberUsernameChecked() { + return $this->rememberUsernameChecked; + } + /** * Initialize login. diff --git a/modules/core/templates/loginuserpass.php b/modules/core/templates/loginuserpass.php index e2657cd674dac252daff12189938af14dd46e340..76e6d4ceae22446e7630ea4b46e9902adb9dc274 100644 --- a/modules/core/templates/loginuserpass.php +++ b/modules/core/templates/loginuserpass.php @@ -40,13 +40,44 @@ if ($this->data['forceUsername']) { } ?> </td> - <td style="padding: .4em;" rowspan="3"> - <input type="submit" tabindex="4" value="<?php echo $this->t('{login:login_button}'); ?>" /> +<?php +if ($this->data['rememberUsernameEnabled']) { + $rowspan = 1; +} elseif (array_key_exists('organizations', $this->data)) { + $rowspan = 3; +} else { + $rowspan = 2; +} +?> + <td style="padding: .4em;" rowspan="<?php echo $rowspan; ?>"> +<?php +if ($this->data['rememberUsernameEnabled']) { + echo str_repeat("\t", 4); + echo '<input type="checkbox" id="remember_username" tabindex="4" name="remember_username" value="Yes" '; + echo ($this->data['rememberUsernameChecked'] ? 'checked="Yes" /> ' : '/> '); + echo $this->t('{login:remember_username}'); +} else { + $text = $this->t('{login:login_button}'); + echo str_repeat("\t", 4); + echo "<input type=\"submit\" tabindex=\"4\" value=\"{$text}\" />"; +} +?> </td> </tr> <tr> <td style="padding: .3em;"><?php echo $this->t('{login:password}'); ?></td> <td><input id="password" type="password" tabindex="2" name="password" /></td> +<?php +// Move submit button to next row if remember checkbox enabled +if ($this->data['rememberUsernameEnabled']) { + $rowspan = (array_key_exists('organizations', $this->data) ? 2 : 1); +?> + <td style="padding: .4em;" rowspan="<?php echo $rowspan; ?>"> + <input type="submit" tabindex="5" value="<?php echo $this->t('{login:login_button}'); ?>" /> + </td> +<?php +} +?> </tr> <?php diff --git a/modules/core/www/loginuserpass.php b/modules/core/www/loginuserpass.php index 4e41a13053a0559de93abb2eca2c2ffedc3871af..77f1b91277916346fa98b368c63fc8290c18f89e 100644 --- a/modules/core/www/loginuserpass.php +++ b/modules/core/www/loginuserpass.php @@ -27,6 +27,8 @@ if ($source === NULL) { if (array_key_exists('username', $_REQUEST)) { $username = $_REQUEST['username']; +} elseif ($source->getRememberUsernameEnabled() && array_key_exists($source->getAuthId() . '-username', $_COOKIE)) { + $username = $_COOKIE[$source->getAuthId() . '-username']; } elseif (isset($state['core:username'])) { $username = (string)$state['core:username']; } else { @@ -46,6 +48,14 @@ if (!empty($_REQUEST['username']) || !empty($password)) { $username = $state['forcedUsername']; } + if ($source->getRememberUsernameEnabled()) { + $sessionHandler = SimpleSAML_SessionHandler::getSessionHandler(); + $params = $sessionHandler->getCookieParams(); + $params['expire'] = time(); + $params['expire'] += (isset($_REQUEST['remember_username']) && $_REQUEST['remember_username'] == 'Yes' ? 31536000 : -300); + setcookie($source->getAuthId() . '-username', $username, $params['expire'], $params['path'], $params['domain'], $params['secure'], $params['httponly']); + } + $errorCode = sspmod_core_Auth_UserPassBase::handleLogin($authStateId, $username, $password); } else { $errorCode = NULL; @@ -57,9 +67,14 @@ $t->data['stateparams'] = array('AuthState' => $authStateId); if (array_key_exists('forcedUsername', $state)) { $t->data['username'] = $state['forcedUsername']; $t->data['forceUsername'] = TRUE; + $t->data['rememberUsernameEnabled'] = FALSE; + $t->data['rememberUsernameChecked'] = FALSE; } else { $t->data['username'] = $username; $t->data['forceUsername'] = FALSE; + $t->data['rememberUsernameEnabled'] = $source->getRememberUsernameEnabled(); + $t->data['rememberUsernameChecked'] = $source->getRememberUsernameChecked(); + if (isset($_COOKIE[$source->getAuthId() . '-username'])) $t->data['rememberUsernameChecked'] = TRUE; } $t->data['links'] = $source->getLoginLinks(); $t->data['errorcode'] = $errorCode; diff --git a/modules/core/www/loginuserpassorg.php b/modules/core/www/loginuserpassorg.php index f3713db4f5ded706a0d1fc77fdc9dc06982caa29..b1d1a083183ac3bdfaed9bfe34dd276a562ae83f 100644 --- a/modules/core/www/loginuserpassorg.php +++ b/modules/core/www/loginuserpassorg.php @@ -18,10 +18,17 @@ $authStateId = $_REQUEST['AuthState']; /* Retrieve the authentication state. */ $state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_core_Auth_UserPassOrgBase::STAGEID); +$source = SimpleSAML_Auth_Source::getById($state[sspmod_core_Auth_UserPassOrgBase::AUTHID]); +if ($source === NULL) { + throw new Exception('Could not find authentication source with id ' . $state[sspmod_core_Auth_UserPassOrgBase::AUTHID]); +} + $organizations = sspmod_core_Auth_UserPassOrgBase::listOrganizations($authStateId); if (array_key_exists('username', $_REQUEST)) { $username = $_REQUEST['username']; +} elseif ($source->getRememberUsernameEnabled() && array_key_exists($source->getAuthId() . '-username', $_COOKIE)) { + $username = $_COOKIE[$source->getAuthId() . '-username']; } elseif (isset($state['core:username'])) { $username = (string)$state['core:username']; } else { @@ -45,6 +52,15 @@ if (array_key_exists('organization', $_REQUEST)) { $errorCode = NULL; if ($organizations === NULL || !empty($organization)) { if (!empty($username) && !empty($password)) { + + if ($source->getRememberUsernameEnabled()) { + $sessionHandler = SimpleSAML_SessionHandler::getSessionHandler(); + $params = $sessionHandler->getCookieParams(); + $params['expire'] = time(); + $params['expire'] += (isset($_REQUEST['remember_username']) && $_REQUEST['remember_username'] == 'Yes' ? 31536000 : -300); + setcookie($source->getAuthId() . '-username', $username, $params['expire'], $params['path'], $params['domain'], $params['secure'], $params['httponly']); + } + $errorCode = sspmod_core_Auth_UserPassOrgBase::handleLogin($authStateId, $username, $password, $organization); } } @@ -54,6 +70,9 @@ $t = new SimpleSAML_XHTML_Template($globalConfig, 'core:loginuserpass.php'); $t->data['stateparams'] = array('AuthState' => $authStateId); $t->data['username'] = $username; $t->data['forceUsername'] = FALSE; +$t->data['rememberUsernameEnabled'] = $source->getRememberUsernameEnabled(); +$t->data['rememberUsernameChecked'] = $source->getRememberUsernameChecked(); +if (isset($_COOKIE[$source->getAuthId() . '-username'])) $t->data['rememberUsernameChecked'] = TRUE; $t->data['errorcode'] = $errorCode; if ($organizations !== NULL) {