From cdb8cc49982f3ba93478d3556f0b1e8714b31b7c Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Wed, 23 May 2012 14:01:08 +0000
Subject: [PATCH] core:UserPass(Org)Base: Add "remember username"-option.

Thanks to Ryan Panning for implementing this!

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3102 44740490-163a-0410-bde0-09ae8108e29a
---
 config-templates/authsources.php          | 16 +++++++++
 dictionaries/login.definition.json        |  3 ++
 modules/core/lib/Auth/UserPassBase.php    | 42 +++++++++++++++++++++++
 modules/core/lib/Auth/UserPassOrgBase.php | 40 +++++++++++++++++++++
 modules/core/templates/loginuserpass.php  | 35 +++++++++++++++++--
 modules/core/www/loginuserpass.php        | 15 ++++++++
 modules/core/www/loginuserpassorg.php     | 19 ++++++++++
 7 files changed, 168 insertions(+), 2 deletions(-)

diff --git a/config-templates/authsources.php b/config-templates/authsources.php
index 093e9dfaa..87d183419 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 478f852a5..1acb64e10 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 0b63e9283..b7ee4dc74 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 e07eccb43..1c151fff8 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 e2657cd67..76e6d4cea 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 4e41a1305..77f1b9127 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 f3713db4f..b1d1a0831 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) {
-- 
GitLab