Commit ef83f1db authored by Martyn Smith's avatar Martyn Smith Committed by Martyn Smith
Browse files
parents 2fa12387 5292fa22
......@@ -33,10 +33,38 @@ $name = clean_requestdata('name', PARAM_ALPHAEXT, REQUEST_EITHER);
$install = clean_requestdata('install', PARAM_BOOL, REQUEST_EITHER);
if ($install) {
// @todo should probably report errors. Also see upgrade.php to make the js detect any errors
if (!get_config('installed')) {
set_config('installed', true);
try {
// Install the default institution
$institution = new StdClass;
$institution->name = 'mahara';
$institution->displayname = 'No Institution';
$institution->authplugin = 'internal';
insert_record('institution', $institution);
// Insert the root user
$user = new StdClass;
$user->username = 'root';
$user->password = 'mahara';
$user->institution = 'mahara';
$user->passwordchange = 1;
$user->firstname = 'Admin';
$user->lastname = 'User';
$user->email = 'admin@example.org';
insert_record('usr', $user);
set_config('installed', true);
}
catch (SQLException $e) {
echo json_encode(array(
'success' => 0,
'errormessage' => $e->getMessage()
));
exit;
}
}
echo json_encode(array('success' => 1));
exit;
}
......
......@@ -30,6 +30,8 @@ define('INSTALLER', 1);
require(dirname(dirname(__FILE__)).'/init.php');
require(get_config('libroot') . 'upgrade.php');
$smarty = smarty(array('mochikit'));
$upgrades = check_upgrades();
if (!$upgrades) {
die_info(get_string('noupgrades', 'admin'));
......@@ -44,18 +46,47 @@ $loadingstring = get_string('upgradeloading', 'admin');
$successstring = get_string('upgradesuccess', 'admin');
$failurestring = get_string('upgradefailure', 'admin');
// Check if Mahara is being installed. An extra hook is required to insert core
// data if so.
if (!empty($upgrades['core']->install)) {
$smarty->assign('install', true);
$installjs =<<< EOJS
var d = loadJSONDoc('upgrade.json.php', { 'install' : 1 });
$('coredata').innerHTML = '<img src="{$loadingicon}" alt="{$loadingstring}" />';
d.addCallback(function (data) {
if ( data.success ) {
var message = 'Successfully installed core data';
$('coredata').innerHTML = '<img src="{$successicon}" alt=":)" /> ' + message;
}
else {
var message = '';
if (data.errormessage) {
message = data.errormessage;
}
else {
message = '{$failurestring}';
}
$('coredata').innerHTML = '<img src="{$failureicon}" alt=":(" /> ' + message;
}
});
d.addErrback(function () {
$('coredata').innerHTML = '<img src="{$failureicon}" alt=":(" /> {$failurestring}';
});
EOJS;
}
else {
$installjs = '';
}
$js .= <<< EOJS
function processNext() {
var element = todo.shift();
if ( ! element ) {
// we're done
// @todo this needs work:
// - should only hit upgrade.json.php with install message
// if we are actually installing - can check $upgrades
// in this file for that
loadJSONDoc('upgrade.json.php', { 'install' : 1 });
// @todo do as a deferred on the above call
$installjs
$('finished').style.display = 'block';
return;
}
......@@ -89,7 +120,6 @@ $js .= <<< EOJS
addLoadEvent( processNext );
EOJS;
$smarty = smarty(array('mochikit'));
$smarty->assign('INLINEJAVASCRIPT', $js);
$smarty->assign_by_ref('upgrades', $upgrades);
......
......@@ -49,8 +49,8 @@ class AuthInternal extends Auth {
* Given a user that we know about, return an array of information about them
*/
public static function get_user_info($username) {
$user = new StdClass;
$user->username = $username;
// @todo: only select the information the session requires
$user = get_record('usr', 'username', $username);
return $user;
}
......@@ -78,7 +78,32 @@ class AuthInternal extends Auth {
// $form->set_error('foo', 'WTF man!');
//}
}
/**
* For internal authentication, usernames can only contain alphanumeric
* characters, and the symbols underscore, full stop and the @ symbol.
*
* The username must also be between three and thirty characters in length.
*
* @param string $username The username to check
* @return bool Whether the username is valid
*/
public static function is_username_valid($username) {
return preg_match('/^[a-zA-Z0-9\._@]{3,30}$/', $username);
}
/**
* For internal authentication, passwords can contain a range of letters,
* numbers and symbols. There is a minimum limit of six characters allowed
* for the password, and no upper limit
*
* @param string $password The password to check
* @return bool Whether the password is valid
*/
public static function is_password_valid($password) {
return preg_match('/^[a-zA-Z0-9 ~!#\$%\^&\*\(\)_\-=\+\,\.<>\/\?;:"\[\]\{\}\\\|`\']{6,}$/', $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
......@@ -92,8 +117,9 @@ class AuthInternal extends Auth {
*
* @param string $password The password to encrypt
* @param string $salt The salt to use to encrypt the password
* @todo salt mandatory
*/
private static function encrypt_password($password, $salt='') {
public static function encrypt_password($password, $salt='') {
if ($salt == '') {
$salt = substr(md5(rand(1000000, 9999999)), 2, 8);
}
......@@ -122,7 +148,7 @@ class AuthInternal extends Auth {
}
// The main type - a salted sha1
$sha1sent = Auth_Internal::encrypt_password($theysent, $salt);
$sha1sent = self::encrypt_password($theysent, $salt);
return $sha1sent == $wehave;
}
......
......@@ -210,7 +210,9 @@ function auth_setup () {
}
// The session is still active, so continue it.
log_debug('session still active from previous time');
return $SESSION->renew();
$USER = $SESSION->renew();
auth_check_password_change();
return $USER;
}
else if ($sessionlogouttime > 0) {
// The session timed out
......@@ -234,6 +236,7 @@ function auth_setup () {
$form = new Form(auth_get_login_form());
if ($USER) {
log_debug('user logged in just fine');
auth_check_password_change();
return;
}
......@@ -254,6 +257,139 @@ function auth_get_authtype_for_institution($institution) {
return 'internal';
}
/**
* Checks whether the current user needs to change their password, and handles
* the password changing if it's required.
*
* This only applies for the internal authentication plugin. Other plugins
* will, in theory, have different data stores, making changing the password
* via the internal form difficult.
*/
function auth_check_password_change() {
global $SESSION;
log_debug('checking if the user needs to change their password');
log_debug($SESSION);
if (auth_get_authtype_for_institution($SESSION->get('institution')) == 'internal' && $SESSION->get('passwordchange')) {
log_debug('user DOES need to change their password');
require_once('form.php');
$form = array(
'name' => 'change_password',
'method' => 'post',
'elements' => array(
'password1' => array(
'type' => 'password',
'title' => 'New Password:',
'description' => 'Your new password',
'rules' => array(
'required' => true
)
),
'password2' => array(
'type' => 'password',
'title' => 'Confirm Password:',
'description' => 'Your new password again',
'rules' => array(
'required' => true
)
),
'submit' => array(
'type' => 'submit',
'value' => 'Change Password'
)
)
);
$smarty = smarty();
$smarty->assign('change_password_form', form($form));
$smarty->display('change_password.tpl');
exit;
}
}
/**
* Validates the form for changing the password for a user.
*
* This only applies to the internal authentication plugin.
*
* @todo check that the password isn't something simple, like 'mahara'.
* @param Form $form The form to check
* @param array $values The values to check
*/
function change_password_validate(Form $form, $values) {
global $SESSION;
// 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', 'Your password is not in a valid form');
}
// 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', 'Your password is too easy! Please choose a harder password');
}
// The password cannot be the same as the old one
if (!$form->get_error('password1') && $values['password1'] == get_field('usr', 'password', 'username', $SESSION->get('username'))) {
$form->set_error('password1', 'Your did not change your password!');
}
// The passwords must match
if (!$form->get_error('password1') && !$form->get_error('password2') && $values['password1'] != $values['password2']) {
$form->set_error('password2', 'Your passwords do not match');
}
}
else {
throw new Exception('The user "' . $USER->username . '" is trying to'
. ' change their password, but they do not use the internal'
. ' authentication method');
}
}
/**
* 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;
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');
$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->passwordchange = 0;
$where = new StdClass;
$where->username = $SESSION->get('username');
update_record('usr', $user, $where);
$SESSION->set('passwordchange', 0);
$SESSION->add_ok_msg('Your new password has been saved');
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');
}
}
/**
* Creates and displays the transient login page.
*
......
......@@ -14,11 +14,34 @@
<KEY NAME="primary" TYPE="primary" FIELDS="field" />
</KEYS>
</TABLE>
<TABLE NAME="auth_installed">
<FIELDS>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="version" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="release" TYPE="text" LENGTH="small" NOTNULL="true" />
<FIELD NAME="active" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" />
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="name" />
</KEYS>
</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" />
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="name" />
<KEY NAME="pluginfk" TYPE="foreign" FIELDS="authplugin" REFTABLE="auth_installed" REFFIELDS="name" />
</KEYS>
</TABLE>
<TABLE NAME="usr">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" />
<FIELD NAME="username" TYPE="char" LENGTH="30" NOTNULL="true" />
<FIELD NAME="password" TYPE="char" LENGTH="40" NOTNULL="true" />
<FIELD NAME="institution" TYPE="char" LENGTH="255" NOTNULL="true"/>
<FIELD NAME="salt" TYPE="char" LENGTH="8" />
<FIELD NAME="passwordchange" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" />
<FIELD NAME="deleted" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" />
......@@ -33,6 +56,7 @@
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" />
<KEY NAME="institution" TYPE="foreign" FIELDS="institution" REFTABLE="institution" REFFIELDS="name"/>
</KEYS>
<INDEXES>
<INDEX NAME="usernameuk" UNIQUE="true" FIELDS="LOWER(username)"/>
......@@ -84,17 +108,6 @@
</KEYS>
</TABLE>
<!-- auth plugin tables -->
<TABLE NAME="auth_installed">
<FIELDS>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="version" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="release" TYPE="text" LENGTH="small" NOTNULL="true" />
<FIELD NAME="active" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" />
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="name" />
</KEYS>
</TABLE>
<TABLE NAME="auth_config">
<FIELDS>
<FIELD NAME="plugin" TYPE="char" LENGTH="100" NOTNULL="true" />
......@@ -623,17 +636,6 @@
<KEY NAME="communityfk" TYPE="foreign" FIELDS="community" REFTABLE="community" REFFIELDS="id" />
</KEYS>
</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="plugin" TYPE="char" LENGTH="255" NOTNULL="true" />
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="name" />
<KEY NAME="pluginfk" TYPE="foreign" FIELDS="plugin" REFTABLE="auth_installed" REFFIELDS="name" />
</KEYS>
</TABLE>
</TABLES>
<STATEMENTS>
<STATEMENT NAME="insert config" TYPE="insert" TABLE="config">
......@@ -641,11 +643,6 @@
<SENTENCE TEXT="(field, value) VALUES ('session_timeout', 1800)" />
</SENTENCES>
</STATEMENT>
<STATEMENT NAME="insert root user" TYPE="insert" TABLE="usr">
<SENTENCES>
<SENTENCE TEXT="(username, password, passwordchange, firstname, lastname, email) VALUES ('root', 'mahara', 1, 'Admin', 'User', 'admin@example.com')"/>
</SENTENCES>
</STATEMENT>
<STATEMENT NAME="insert event types" TYPE="insert" TABLE="event_type">
<SENTENCES>
<SENTENCE TEXT="(name) VALUES ('createuser')" />
......
......@@ -1174,4 +1174,17 @@ function is_mysql() {
return (strpos(get_config('dbtype'), 'mysql') === 0);
}
/**
* function to convert an array to
* an array of placeholders (?)
* with the right number of values
*
* @param array $array input array
*/
function db_array_to_ph($array) {
return array_pad(array(), count($array), '?');
}
?>
......@@ -130,6 +130,13 @@ class Form {
*/
private $action = '';
/**
* The javascript function that the form will be submitted to.
*
* @var string
*/
private $onsubmit = '';
/**
* The tab index for this particular form.
*
......@@ -202,6 +209,7 @@ class Form {
$form_defaults = array(
'method' => 'post',
'action' => '',
'onsubmit' => '',
'elements' => array()
);
$data = array_merge($form_defaults, $data);
......@@ -212,8 +220,8 @@ class Form {
$data['method'] = 'get';
}
$this->method = $data['method'];
$this->action = $data['action'];
$this->onsubmit = $data['onsubmit'];
if (isset($data['tabindex'])) {
$this->tabindex = intval($data['tabindex']);
......@@ -364,9 +372,10 @@ class Form {
*/
public function build() {
$result = '<form';
foreach (array('name', 'method', 'action') as $attribute) {
foreach (array('name', 'method', 'action', 'onsubmit') as $attribute) {
$result .= ' ' . $attribute . '="' . $this->{$attribute} . '"';
}
$result .= ' id="' . $this->name . '"';
if ($this->fileupload) {
$result .= ' enctype="multipart/form-data"';
}
......@@ -634,7 +643,7 @@ class Form {
* @return string The attributes for the element
*/
public static function element_attributes($element, $exclude=array()) {
static $attributes = array('accesskey', 'class', 'dir', 'id', 'lang', 'maxlength', 'name', 'size', 'style', 'tabindex');
static $attributes = array('accesskey', 'class', 'dir', 'id', 'lang', 'maxlength', 'name', 'onclick', 'size', 'style', 'tabindex');
$elementattributes = array_diff($attributes, $exclude);
$result = '';
foreach ($elementattributes as $attribute) {
......@@ -722,15 +731,18 @@ function form_render_element($element, Form $form) {
throw new FormException('No form renderer specified for form "' . $form->get_name() . '"');
}
$element['id'] = Form::make_id($element);
$element['class'] = Form::make_class($element);
$newelement = $element;
$newelement['class'] = (isset($newelement['class'])
? $newelement['class'] . ' ' . $form->get_name() : '');
$builtelement = $function($newelement, $form);
// Prepare the prefix and suffix
$prefix = (isset($element['prefix'])) ? $element['prefix'] : '';
$suffix = (isset($element['suffix'])) ? $element['suffix'] : '';
return $prefix . $rendererfunction($function($element, $form), $element) . $suffix;
return $prefix . $rendererfunction($builtelement, $element) . $suffix;
}
?>
......@@ -578,6 +578,20 @@ function hsc ($text) {
return htmlspecialchars($text, ENT_COMPAT, 'UTF-8');
}
/**
* function to convert an array of objects to
* an array containing one field per place
*
* @param array $array input array
* @param mixed $field field to look for in each object
*/
function mixed_array_to_field_array($array, $field) {
$repl_fun = create_function('$n, $field', '$n = (object)$n; return $n->{$field};');
$fields = array_pad(array(), count($array), $field);
return array_map($repl_fun, $array, $fields);
}
/**
* Used by XMLDB
*/
......
......@@ -56,8 +56,10 @@ class Session {
*/
public function __construct() {
$this->defaults = array(
'logout_time' => 0,
'username' => ''
'logout_time' => 0,
'username' => '',
'passwordchange' => false,
'institution' => 'mahara'
);
// Resume an existing session if required
if (isset($_COOKIE['PHPSESSID'])) {
......
<?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 notification/email
* @author Penny Leach <penny@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 2006,2007 Catalyst IT Ltd http://catalyst.net.nz
*
*/
defined('INTERNAL') || die();
require_once(get_config('docroot') . 'notification/lib.php');
class PluginNotificationEmail extends PluginNotification {
public static function notify_user($user, $data) {
// @todo
}
}
?>
<?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 or plugintype/pluginname
* @author Your Name <you@example.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 2006,2007 Catalyst IT Ltd http://catalyst.net.nz
*
*/
defined(