Commit 6b873115 authored by Penny Leach's avatar Penny Leach
Browse files
parents ccce3d0b bab165f9
......@@ -24,11 +24,11 @@
*
*/
define('INTERNAL',1);
define('INTERNAL', 1);
// uncomment if this page is public (doesn't require login)
// defined('PUBLIC', 1);
// define('PUBLIC', 1);
// uncomment and set if this page isn't public
// defined('MENUITEM', 'TODO');
// define('MENUITEM', 'TODO');
require('init.php');
// Your code here
......
......@@ -32,5 +32,6 @@ $string['passwordtooeasy'] = 'Your password is too easy! Please choose a harder
$string['passwordnotchanged'] = 'You did not change your password, please choose a new password';
$string['passwordsaved'] = 'Your new password has been saved';
$string['passwordsdonotmatch'] = 'The passwords do not match';
$string['usernameinvalidform'] = 'Your username may only include alphanumeric characters, full stops, underscores and @ symbols';
?>
......@@ -47,10 +47,11 @@ class AuthInternal extends Auth {
/**
* Given a user that we know about, return an array of information about them
*
* NOTE: Does not need to be implemented for the internal authentication
* method, as by default information is sourced from the database.
*/
public static function get_user_info($username) {
$user = get_record('usr', 'username', $username, null, null, null, null, '*, ' . db_format_tsfield('expiry'));
return $user;
}
/**
......@@ -91,6 +92,16 @@ class AuthInternal extends Auth {
return preg_match('/^[a-zA-Z0-9\._@]{3,30}$/', $username);
}
/**
* Returns information about whether the given user is suspended
*
* @param object $user The user to check
* @return object Information relating to whether the user is suspended
*/
public static function is_user_suspended($user) {
return get_record('usr_suspension', 'usr', $user->id);
}
/**
* For internal authentication, passwords can contain a range of letters,
* numbers and symbols. There is a minimum limit of six characters allowed
......@@ -115,6 +126,22 @@ class AuthInternal extends Auth {
return true;
}
/**
* Changes the user's password.
*
* @return string The new password, or empty if the password could not be set
*/
public static function change_password($user, $password) {
// Create a salted password and set it for the user
$updateuser = new StdClass;
$updateuser->salt = substr(md5(rand(1000000, 9999999)), 2, 8);
$updateuser->password = self::encrypt_password($password, $updateuser->salt);
$where = new StdClass;
$where->username = $user->username;
update_record('usr', $updateuser, $where);
return $updateuser->password;
}
/*
The following two functions are inspired by Andrew McMillan's salted md5
functions in AWL, adapted with his kind permission. Changed to use sha1
......
......@@ -36,6 +36,12 @@ class AuthUnknownUserException extends Exception {}
/**
* Base authentication class. Provides a common interface with which
* authentication can be carried out for system users.
*
* @todo for authentication:
* - inactivity: each institution has inactivity timeout times, this needs
* to be supported
* - this means the lastlogin field needs to be updated on the usr table
* - warnings are handled by cron
*/
abstract class Auth {
......@@ -216,7 +222,8 @@ function auth_setup () {
// The session is still active, so continue it.
log_debug('session still active from previous time');
$USER = $SESSION->renew();
auth_check_password_change($USER);
log_debug($USER);
auth_check_password_change();
return $USER;
}
else if ($sessionlogouttime > 0) {
......@@ -257,11 +264,13 @@ function auth_setup () {
* Given an institution, returns the authentication method used by it.
*
* @return string
* @todo<nigel>: Currently, the system doesn't have a concept of institution
* at the database level, so the internal authentication method is assumed.
*/
function auth_get_authtype_for_institution($institution) {
return 'internal';
static $cache = array();
if (isset($cache[$institution])) {
return $cache[$institution];
}
return $cache[$institution] = get_field('institution', 'authplugin', 'name', $institution);
}
/**
......@@ -272,10 +281,28 @@ function auth_get_authtype_for_institution($institution) {
* will, in theory, have different data stores, making changing the password
* via the internal form difficult.
*/
function auth_check_password_change($user) {
function auth_check_password_change() {
global $USER;
log_debug('checking if the user needs to change their password');
if (auth_get_authtype_for_institution($user->institution) == 'internal' && $user->passwordchange) {
if (!$USER->passwordchange) {
return;
}
$authtype = auth_get_authtype_for_institution($USER->institution);
$authclass = 'Auth' . ucfirst($authtype);
$url = '';
safe_require('auth', $authtype, 'lib.php', 'require_once');
// @todo auth preference for a password change screen for all auth methods other than internal
if (
($url = get_config_plugin('auth', $authtype, 'changepasswordurl'))
|| (method_exists($authclass, 'change_password'))) {
log_debug('user DOES need to change their password');
if ($url) {
redirect($url);
exit;
}
require_once('form.php');
$form = array(
'name' => 'change_password',
......@@ -311,41 +338,6 @@ function auth_check_password_change($user) {
}
}
/**
* Check if the given user's account has expired
*
* @param object $user The user to check for the expired password.
* @todo maybe later, just use $USER because that's all we are actually checking...
* @private
*/
function auth_check_user_expired($user) {
log_debug('Checking to see if the user has expired');
if ($user->expiry > 0 && time() > $user->expiry) {
// Trash the $USER object, used for checking if the user is logged in.
// Smarty uses it otherwise...
global $USER;
$USER = null;
die_info(get_string('accountexpired'));
}
}
/**
* Check if the given user's account has been suspended
*
* @param object $user The user to check for the suspended account.
* @private
*/
function auth_check_user_suspended($user) {
global $USER;
log_debug('Checking to see if the user is suspended');
$suspend = get_record('usr_suspension', 'usr', $user->id);
if ($suspend) {
global $USER;
$USER = null;
die_info(get_string('accountsuspended', 'mahara', $suspend->ctime, $suspend->reason));
}
}
/**
* Validates the form for changing the password for a user.
*
......@@ -353,14 +345,19 @@ function auth_check_user_suspended($user) {
*
* @todo As far as I can tell, the change password and registration forms will
* only be used for internal authentication. And so, by proxy, will the
* username/password valid methods for the Auth class. I think this means they
* can be removed from the Auth class, and instead just be part of AuthInternal
* since they don't need to be specified for other types.
* username/password valid methods for the Auth class. [THIS IS TRUE]
*
*
* I think this means they can be removed from the Auth class, and instead just
* be part of AuthInternal since they don't need to be specified for other types.
* [THIS IS ALSO TRUE]
*
* Furthermore, I think that the change_password stuff (as well as suspended
* and expired) are also quite possibly related to internal only. This will
* require a lot of thought about how to best structure it.
*
* Change password will only be if a URL for it exists, or a function exists
*
* @param Form $form The form to check
* @param array $values The values to check
*/
......@@ -369,74 +366,64 @@ function change_password_validate(Form $form, $values) {
// Get the authentication type for the user (based on the institution), and
// use the information to validate the password
$authtype = auth_get_authtype_for_institution($SESSION->get('institution'));
if ($authtype == 'internal') {
safe_require('auth', $authtype, 'lib.php', 'require_once');
// Check that the password is in valid form
if (!$form->get_error('password1')
&& !call_static_method('AuthInternal', 'is_password_valid', $values['password1'])) {
$form->set_error('password1', get_string('passwordinvalidform', 'auth.internal'));
}
$authtype = auth_get_authtype_for_institution($SESSION->get('institution'));
$authclass = 'Auth' . ucfirst($authtype);
$authlang = 'auth.' . $authtype;
safe_require('auth', $authtype, 'lib.php', 'require_once');
// The password must not be too easy :)
$suckypasswords = array(
'mahara', 'password', $SESSION->get('username')
);
if (!$form->get_error('password1') && in_array($values['password1'], $suckypasswords)) {
$form->set_error('password1', get_string('passwordtooeasy', 'auth.internal'));
}
// Check that the password is in valid form
if (!$form->get_error('password1')
&& !call_static_method($authclass, 'is_password_valid', $values['password1'])) {
$form->set_error('password1', get_string('passwordinvalidform', $authlang));
}
// The password cannot be the same as the old one
if (!$form->get_error('password1') && $values['password1'] == $USER->password) {
$form->set_error('password1', get_string('passwordnotchanged', 'auth.internal'));
}
// The password must not be too easy :)
$suckypasswords = array(
'mahara', 'password', $SESSION->get('username')
);
if (!$form->get_error('password1') && in_array($values['password1'], $suckypasswords)) {
$form->set_error('password1', get_string('passwordtooeasy'));
}
// The passwords must match
if (!$form->get_error('password1') && !$form->get_error('password2') && $values['password1'] != $values['password2']) {
$form->set_error('password2', get_string('passwordsdonotmatch', 'auth.internal'));
}
// The password cannot be the same as the old one
if (!$form->get_error('password1') && $values['password1'] == $SESSION->get('password')) {
$form->set_error('password1', get_string('passwordnotchanged'));
}
else {
throw new Exception('The user "' . $USER->username . '" is trying to'
. ' change their password, but they do not use the internal'
. ' authentication method');
// The passwords must match
if (!$form->get_error('password1') && !$form->get_error('password2') && $values['password1'] != $values['password2']) {
$form->set_error('password2', get_string('passwordsdonotmatch'));
}
}
/**
* Changes the password for a user, given that it is valid.
*
* This only applies to the internal authentication plugin.
*
* @param array $values The submitted form values
*/
function change_password_submit($values) {
global $SESSION;
global $SESSION, $USER;
log_debug('changing password to ' . $values['password1']);
$authtype = auth_get_authtype_for_institution($SESSION->get('institution'));
if ($authtype == 'internal') {
// Create a salted password and set it for the user
safe_require('auth', $authtype, 'lib.php', 'require_once');
$authclass = 'Auth' . ucfirst($authtype);
// This method should exists, because if it did not then the change
// password form would not have been shown.
if ($password = call_static_method($authclass, 'change_password', $USER, $values['password1'])) {
$user = new StdClass;
$user->salt = substr(md5(rand(1000000, 9999999)), 2, 8);
$user->password = call_static_method('AuthInternal', 'encrypt_password', $values['password1'], $user->salt);
$user->password = $password;
$user->passwordchange = 0;
$where = new StdClass;
$where->username = $SESSION->get('username');
update_record('usr', $user, $where);
$SESSION->set('password', $password);
$SESSION->set('passwordchange', 0);
$SESSION->add_ok_msg(get_string('passwordsaved', 'auth.internal'));
$SESSION->add_ok_msg(get_string('passwordsaved'));
redirect(get_config('wwwroot'));
exit;
}
else {
throw new Exception('The user "' . $USER->username . '" is trying to'
. ' change their password, but they do not use the internal'
. ' authentication method');
}
throw new Exception('You are trying to change your password, but the attempt failed');
}
/**
......@@ -519,6 +506,9 @@ function auth_get_login_form() {
'submit' => array(
'type' => 'submit',
'value' => get_string('login')
),
'register' => array(
'value' => '<tr><td colspan="2"><a href="' . get_config('wwwroot') . 'register.php">' . get_string('register') . '</a></td></tr>'
)
);
......@@ -579,7 +569,7 @@ function login_submit($values) {
log_debug('auth details supplied, attempting to log user in');
$username = $values['login_username'];
$password = $values['login_password'];
$institution = (isset($values['login_institution'])) ? $values['login_institution'] : 0;
$institution = (isset($values['login_institution'])) ? $values['login_institution'] : 'mahara';
$authtype = auth_get_authtype_for_institution($institution);
safe_require('auth', $authtype, 'lib.php', 'require_once');
......@@ -588,12 +578,57 @@ function login_submit($values) {
try {
if (call_static_method($authclass, 'authenticate_user_account', $username, $password, $institution)) {
log_debug('user ' . $username . ' logged in OK');
$USER = call_static_method($authclass, 'get_user_info', $username);
auth_check_user_expired($USER);
auth_check_user_suspended($USER);
if (!record_exists('usr', 'username', $username)) {
// We don't know about this user. But if the authentication
// method says they're fine, then we must insert data for them
// into the usr table.
log_debug('this user authenticated but not in the usr table, adding them');
// @todo document what needs to be returned by get_user_info
$USER = call_static_method($authclass, 'get_user_info', $username);
log_debug($USER);
insert_record('usr', $USER);
}
// @todo config form option for this for each external plugin. NOT for internal
else if (get_config_plugin('auth', $authtype, 'updateuserinfoonlogin')) {
log_debug('updating user info from auth method');
$USER = call_static_method($authclass, 'get_user_info', $username);
log_debug($USER);
$where = new StdClass;
$where->username = $username;
$where->institution = $institution;
// @todo as per the above todo about what needs to be returned by get_user_info,
// that needs to be validated somewhere. Because here we do an insert into the
// usr table, that needs to work. and provide enough information for future
// authentication attempts
update_record('usr', $USER, $where);
}
else {
log_debug('getting user info from database');
$USER = get_record('usr', 'username', $username, null, null, null, null, '*, ' . db_format_tsfield('expiry'));
log_debug($USER);
}
// Check if the user's account has expired
log_debug('Checking to see if the user has expired');
if ($USER->expiry > 0 && time() > $USER->expiry) {
// Trash the $USER object, used for checking if the user is logged in.
// Smarty uses it and puts login-only stuff in the output otherwise...
$USER = null;
die_info(get_string('accountexpired'));
}
// Check if the user's account has been suspended
log_debug('Checking to see if the user is suspended');
if ($suspend = call_static_method($authclass, 'is_user_suspended', $USER)) {
$USER = null;
die_info(get_string('accountsuspended', 'mahara', $suspend->ctime, $suspend->reason));
}
// User is allowed to log in
$SESSION->login($USER);
$USER->logout_time = $SESSION->get('logout_time');
auth_check_password_change($USER);
auth_check_password_change();
}
else {
// Login attempt failed
......
......@@ -39,8 +39,48 @@ if (!get_config('installed')) {
// this, we can guarantee whether the user is logged in or not for this page.
if (!$SESSION->is_logged_in()) {
require_once('form.php');
$form = auth_get_login_form();
$form['renderer'] = 'div';
$form = array(
'name' => 'login',
'method' => 'post',
'action' => '',
'renderer' => 'div',
'submit' => false,
'elements' => array(
'login' => array(
'type' => 'fieldset',
'legend' => get_string('login'),
'elements' => array(
'login_username' => array(
'type' => 'text',
'title' => get_string('username'),
'description' => get_string('usernamedesc'),
'help' => get_string('usernamehelp'),
'rules' => array(
'required' => true
)
),
'login_password' => array(
'type' => 'password',
'title' => get_string('password'),
'description' => get_string('passworddesc'),
'help' => get_string('passwordhelp'),
'value' => '',
'rules' => array(
'required' => true
)
)
)
),
'submit' => array(
'type' => 'submit',
'value' => get_string('login')
),
'register' => array(
'value' => '<div><a href="' . get_config('wwwroot') . 'register.php">' . get_string('register') . '</a></div>'
)
)
);
$login_form = form($form);
$pagename = 'loggedouthome';
}
......
......@@ -27,9 +27,14 @@
</TABLE>
<TABLE NAME="institution">
<FIELDS>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="displayname" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="authplugin" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true"/>
<FIELD NAME="displayname" TYPE="char" LENGTH="255" NOTNULL="true"/>
<FIELD NAME="authplugin" TYPE="char" LENGTH="255" NOTNULL="true"/>
<FIELD NAME="registerallowed" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1"/>
<FIELD NAME="updateuserinfoonlogin" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0"/>
<FIELD NAME="defaultaccountlifetime" TYPE="datetime" NOTNULL="false"/>
<FIELD NAME="defaultaccountinactiveexpire" TYPE="int" NOTNULL="false"/>
<FIELD NAME="defaultaccountinactivewarn" TYPE="int" NOTNULL="false" DEFAULT="604800"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="name" />
......
......@@ -130,6 +130,14 @@ class Form {
*/
private $action = '';
/**
* Whether the form should be checked for submission. This is useful if you
* just want to build a form, that is possibly validated elsewhere.
*
* @var bool
*/
private $submit = true;
/**
* The javascript function that the form will be submitted to.
*
......@@ -207,9 +215,10 @@ class Form {
// Assign defaults for the form
$form_defaults = array(
'method' => 'post',
'action' => '',
'method' => 'post',
'action' => '',
'onsubmit' => '',
'submit' => true,
'elements' => array()
);
$data = array_merge($form_defaults, $data);
......@@ -221,6 +230,7 @@ class Form {
}
$this->method = $data['method'];
$this->action = $data['action'];
$this->submit = $data['submit'];
$this->onsubmit = $data['onsubmit'];
if (isset($data['renderer'])) {
......@@ -288,7 +298,7 @@ class Form {
// Check if the form was submitted, and if so, validate and process it
$global = ($this->method == 'get') ? $_GET: $_POST;
if (isset($global['form_' . $this->name] )) {
if ($this->submit && isset($global['form_' . $this->name] )) {
$this->submitted = true;
// Check if the form has been cancelled
if ($this->iscancellable) {
......
<?php
/**
* This program is part of Mahara
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* @package mahara
* @subpackage core
* @author Nigel McNie <nigel@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 2006,2007 Catalyst IT Ltd http://catalyst.net.nz
*
*/
define('INTERNAL', 1);
define('PUBLIC', 1);
require('init.php');
$elements = array(
'username' => array(
'type' => 'text',
'title' => get_string('username'),
'description' => get_string('usernamedescription'),
'rules' => array(
'required' => true
)
),
'password1' => array(
'type' => 'password',
'title' => get_string('password'),
'description' => get_string('passworddescription'),
'rules' => array(
'required' => true
)
),
'password2' => array(
'type' => 'password',
'title' => get_string('confirmpassword'),
'description' => get_string('password2description'),
'rules' => array(
'required' => true
)
),
'firstname' => array(
'type' => 'text',
'title' => get_string('firstname'),
'description' => get_string('firstnamedescription'),
'rules' => array(
'required' => true
)
),
'lastname' => array(
'type' => 'text',
'title' => get_string('lastname'),
'description' => get_string('lastnamedescription'),
'rules' => array(
'required' => true
)
),
'email' => array(
'type' => 'text',
'title' => get_string('emailaddress'),
'description' => get_string('emailaddressdescription'),
'rules' => array(
'required' => true,
'email' => true
)
)
);
$institutions = get_records('institution', 'registerallowed', true);
if (count($institutions) > 1) {
$options = array();
foreach ($institutions as $institution) {
$options[$institution->name] = $institution->displayname;
}
$elements['institution'] = array(
'type' => 'select',
'title' => get_string('institution'),
'description' => get_string('institutiondescription'),
'options' => $options
);
}
else {
$elements['institution'] = array(
'type' => 'hidden',
'value' => 'mahara'
);
}
$elements['tandc'] = array(
'type' => 'radio',
'title' =>