*
* AuthUser account self-registration and management
*
* For more information, please see the online documentation at
* http://www.pmwiki.org/wiki/Cookbook/UserAdmin
*
*
* This script 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 3 of the License, or
* (at your option) any later version.
*
* This script 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, see
\n$[UA_return_link_title]",
'UA_return_link_title' => 'Return to user actions',
'UA_txt_key' => 'Activation key',
'UA_txt_useremail' => 'E-mail address',
'UA_txt_username' => 'Username',
'UA_txt_useroldpasswd' => 'Current password',
'UA_txt_userpasswd' => 'New password',
'UA_txt_userpasswd2' => 'New password, again',
'UA_unauthorized' => 'Not authorized',
'UA_unsupported_user_format' => 'User data can't be edited',
'UA_wrong_passwd' => 'Password not recognized',
'UAedit_fail' => 'Error updating user account',
'UAedit_submit' => 'Submit',
'UAedit_success' => 'User account updated',
'UAedit_success_unchanged' => 'User account not modified',
'UAedit_title' => 'Edit user account',
'UAmenu_title' => 'User admin',
'UAnew_email_body' => "Welcome \$username!\n
Thank you for registering at $WikiTitle.
To activate your account and confirm your e-mail address, please visit
the following location:
\$link\n",
'UAnew_email_subject' => "Welcome to $WikiTitle",
'UAnew_email_sent' => 'E-mail sent with activation link',
'UAnew_fail' => 'Error creating account',
'UAnew_submit' => 'Sign up',
'UAnew_success' => 'New user account created',
'UAnew_title' => 'Register as a new user',
'UAresetpasswd_email_body' => "To set a new password for the user \$username at $WikiTitle, please
visit the following location:
\$link
If you've received this message in error, please contact the site admin
at <$ScriptUrl>.\n",
'UAresetpasswd_email_empty' => 'User has no e-mail address defined',
'UAresetpasswd_email_subject' => "Password reset for $WikiTitle",
'UAresetpasswd_fail' => 'Error sending reset link',
'UAresetpasswd_submit' => 'Send reset link to user's e-mail address',
'UAresetpasswd_success' => 'Password reset link sent to user's e-mail address',
'UAresetpasswd_title' => 'Reset password',
'UAunlock_already_active' => 'Account is already active',
'UAunlock_bad_key' => 'Bad activation key',
'UAunlock_fail' => 'Error activating account',
'UAunlock_new_passwd' => 'Enter new password',
'UAunlock_submit' => 'Activate/set password',
'UAunlock_success_activated' => 'User account activated',
'UAunlock_success_set_pw' => 'User password set',
'UAunlock_title' => 'Account activation',
));
########################################################################################################################
## framework class
class UserAdmin {
const REQ_NOT_EMPTY = 0;
const REQ_ANY = 1;
const REQ_TWICE = 2; ## implies not empty
const REQ_TWICE_ANY = 3;
const REQ_PRESET = 4; ## implies not empty
var $confirm_email = TRUE;
var $fields = array(
'new' => array(
'username' => UserAdmin::REQ_NOT_EMPTY,
'userpasswd' => UserAdmin::REQ_TWICE,
'useremail' => UserAdmin::REQ_NOT_EMPTY),
'resetpasswd' => array(
'username' => UserAdmin::REQ_NOT_EMPTY),
'unlock' => array(
'username' => UserAdmin::REQ_PRESET,
'key' => UserAdmin::REQ_PRESET),
'newpasswd' => array(
'username' => UserAdmin::REQ_PRESET,
'key' => UserAdmin::REQ_PRESET,
'userpasswd' => UserAdmin::REQ_TWICE),
'edit' => array(
'username' => UserAdmin::REQ_PRESET,
'useroldpasswd' => UserAdmin::REQ_NOT_EMPTY,
'useremail' => UserAdmin::REQ_NOT_EMPTY,
'userpasswd' => UserAdmin::REQ_TWICE_ANY),
);
var $action;
var $input;
function Read($username) { exit('UserAdmin::Read not implemented'); } // virtual
function Write($username, $data, $csum='', $auth='read') { exit('UserAdmin::Write not implemented'); } // virtual
####################################################################################################################
## utility functions
function ExistsInAuthUserPage($username) {
global $AuthUserPageFmt;
$username = trim($username);
if (preg_match('/^\w[^\s:]*$/', $username)) {
SDV($AuthUserPageFmt, '$SiteAdminGroup.AuthUser');
$pn = FmtPageName($AuthUserPageFmt, $pagename);
$apage = ReadPage($pn, READPAGE_CURRENT);
if ($apage && preg_match("/^\s*$username:/m", $apage['text'])) return TRUE;
}
return FALSE;
}
function Exists($username) {
return $this->ExistsInAuthUserPage($username) || $this->Read($username);
}
function MailUser($username, $fmt, $opt = array()) {
$fmt = array_merge(array('head' => 'From: '.XL('UA_email_from')), $fmt);
if ($username) $opt = array_merge($this->Read($username), $opt);
if (empty($opt['useremail'])) return FALSE;
$msg = array();
foreach ($fmt as $fk => $f) {
foreach($opt as $k => $v) $f = preg_replace("/\\$$k\b`?/", $v, $f);
$msg[$fk] = $f;
}
$msg['body'] = preg_replace('/^\t+/m', '', $msg['body']); ## allow pretty XLSDV with indentation
//exit(pre_r($msg));
return mail($opt['useremail'], @$msg['subject'], @$msg['body'], $msg['head']);
}
function MakeActivationKey() { return strval(mt_rand() + 1); }
function MakeActivationLink($username, $key) {
return "{$GLOBALS['ScriptUrl']}?action=user/unlock&username=".urlencode($username).'&key='.urlencode($key);
}
function UserName($pagename, $opt) {
if (empty($opt['username'])) return FALSE;
$n = $opt['username'];
if (method_exists($this, 'ValidName') && !$this->ValidName($n)) return FALSE;
if (!$this->Exists($n)) return FALSE;
return $n;
}
## return TRUE for authenticated user with admin rights
function Superuser($prompt=FALSE) { return FALSE; }
function Authorized($name, $auth='edit') {
global $AuthId;
return ($name && ($AuthId == $name)) || $this->Superuser(TRUE);
}
####################################################################################################################
## input processing
## returns TRUE if form has been posted
function ReadInput($pagename) {
$this->input = array_merge($_GET, $_POST);
$this->input['username'] = $this->Username($pagename, $_REQUEST);
return preg_grep('/^post/', array_keys($_POST));
}
function ValidEmail(&$address) { return !$address || preg_match('/^.+@.+\..+$/', $address); }
function ValidateInput($fmt = '') {
if (!$fmt) $fmt = $this->action;
if (empty($this->fields[$fmt])) return 'UA_fail_unknown_action';
$result = array();
foreach ($this->fields[$fmt] as $k => $req) {
$this->input[$k] = stripmagic($this->input[$k]);
if (!($req & UserAdmin::REQ_ANY) && empty($this->input[$k])) { $result[] = "UA_empty_$k"; continue; }
if (($req & UserAdmin::REQ_TWICE) && (stripmagic(@$this->input["{$k}2"]) != $this->input[$k])) { $result[] = "UA_diff_$k"; continue; }
$vm = preg_replace('/^user(.+)$/e', "'Valid'.ucfirst('$1')", $k);
if (method_exists($this, $vm) && !$this->{$vm}($this->input[$k])) { $result[] = "UA_invalid_$k"; continue; }
}
return $result;
}
####################################################################################################################
## action handlers
function HandleNew($pagename) {
if (!$this->ReadInput($pagename)) return NULL;
$result = $this->ValidateInput();
if ($this->Exists($this->input['username'])) $result[] = 'UA_exists';
if ($result) return $result;
$hash = crypt($this->input['userpasswd']);
if ($this->confirm_email) {
$key = $this->MakeActivationKey();
$link = $this->MakeActivationLink($this->input['username'], $key);
$mail_fmt = array('subject' => XL('UAnew_email_subject'), 'body' => XL('UAnew_email_body'));
$mail_opt = array('username' => $this->input['username'], 'useremail' => $this->input['useremail'], 'key' => $key, 'link' => $link);
if (!$this->MailUser('', $mail_fmt, $mail_opt)) return array('UA_email_fail', 'UA_contact_admin');
$success = array('UAnew_success', 'UAnew_email_sent');
$data = array(
'useremail' => $this->input['useremail'],
'username' => $this->input['username'],
'userkey' => "$key $hash",
);
} else {
$success = 'UAnew_success';
$data = array(
'userpwhash' => $hash,
'useremail' => @$this->input['useremail'],
'username' => $this->input['username'],
);
}
$fail = array('UAnew_fail', 'UA_contact_admin');
return $this->Write($this->input['username'], $data, implode('; ', array_map('XL', (array)$success))) ? $success : $fail;
}
function HandleResetpasswd($pagename) {
$posted = $this->ReadInput($pagename);
if (!$posted) return NULL;
$result = $this->ValidateInput();
if ($result) return $result;
$user = $this->Read($this->input['username']);
if (!$user) return array('UAresetpasswd_fail', 'UA_invalid_username');
if (empty($user['useremail'])) return array('UAresetpasswd_fail', 'UAresetpasswd_email_empty', 'UA_contact_admin');
$key = $this->MakeActivationKey();
$link = $this->MakeActivationLink($user['username'], $key);
$mail_fmt = array('subject' => XL('UAresetpasswd_email_subject'), 'body' => XL('UAresetpasswd_email_body'));
$mail_opt = array('username' => $user['username'], 'useremail' => $user['useremail'], 'key' => $key, 'link' => $link);
if (!$this->MailUser('', $mail_fmt, $mail_opt)) return array('UA_email_fail', 'UA_contact_admin');
return $this->Write($user['username'], array('userkey' => $key), XL('UAresetpasswd_success'))
? 'UAresetpasswd_success'
: array('UAresetpasswd_fail', 'UA_contact_admin');
}
## handles e-mail address verification and password resets
function HandleUnlock($pagename) {
$posted = $this->ReadInput($pagename);
if (empty($this->input['username'])) return NULL;
$user = $this->Read($this->input['username']);
if (!$user) return array('UAunlock_fail', 'UA_invalid_username');
if (empty($user['userkey'])) return array('UAunlock_fail', 'UAunlock_already_active');
$result = $this->ValidateInput();
if ($result) return $result;
$key = preg_replace('/[^0-9]+/', '', $this->input['key']);
if (!preg_match("/^$key( .*)?$/", $user['userkey'], $match)) return array('UAunlock_fail', 'UAunlock_bad_key');
$hash = trim($match[1]);
if (!$hash) {
$this->fields['unlock'] = $this->fields['newpasswd'];
if (!$posted) return NULL;
$result = $this->ValidateInput('newpasswd');
if ($result) return $result;
$hash = crypt($this->input['userpasswd']);
$reset = TRUE;
} else $reset = FALSE;
$result = $reset ? 'UAunlock_success_set_pw' : 'UAunlock_success_activated';
$data = array('userpwhash' => $hash, 'userkey' => '');
return $this->Write($this->input['username'], $data, XL($result)) ? $result : array('UAnew_fail', 'UA_contact_admin');
}
function HandleEdit($pagename) {
$posted = $this->ReadInput($pagename);
if (empty($this->input['username'])) {
$this->fields[$this->action] = array('username' => UserAdmin::REQ_NOT_EMPTY);
return NULL;
}
if (!$this->Authorized($this->input['username'], 'edit')) return array('UAedit_fail', 'UA_unauthorized');
$admin = $this->Superuser();
if ($admin) unset($this->fields[$this->action]['useroldpasswd']);
$user = $this->Read($this->input['username']);
if (!$user) return array('UAedit_fail', $this->Exists($this->input['username']) ? 'UA_unsupported_user_format' : 'UA_invalid_username');
$ef = preg_grep('/passwd|^username$/', array_keys($this->fields[$this->action]), PREG_GREP_INVERT);
$posted = FALSE;
foreach ($ef as $f) {
if (isset($this->input[$f])) $posted = TRUE;
else $this->input[$f] = @$user[$f];
}
if (!$posted) return NULL;
$result = $this->ValidateInput();
if ($result) return $result;
if (!$admin && (_crypt($this->input['useroldpasswd'], $user['userpwhash']) != $user['userpwhash'])) return 'UA_wrong_passwd';
$data = array();
foreach ($this->fields[$this->action] as $f => $req) {
if (($req & UserAdmin::REQ_PRESET) || preg_match('/passwd/', $f)) continue;
if (@$this->input[$f] === @$user[$f]) continue;
$data[$f] = @$this->input[$f];
}
if (!empty($this->input['userpasswd'])) $data['userpwhash'] = crypt($this->input['userpasswd']);
if (!$data) return 'UAedit_success_unchanged';
return $this->Write($user['username'], $data, XL('UAedit_success'))
? 'UAedit_success'
: array('UAedit_fail', 'UA_contact_admin');
}
function HandleMenu($pagename) {
global $AuthId, $PCache, $UserAdminAnonActions, $UserAdminFmt;
$actions = array();
foreach(preg_grep('/^Handle/', get_class_methods($this)) as $h) {
if ($h == 'HandleMenu') continue;
$actions[] = preg_replace('/^Handle(.)/e', "strtolower('$1')", $h);
}
if (!empty($AuthId) && empty($_GET['list-all'])) {
SDV($UserAdminAnonActions, array('new', 'resetpasswd', 'unlock'));
$actions = array_diff($actions, $UserAdminAnonActions);
}
if (empty($UserAdminFmt)) $UserAdminFmt = '';
$UserAdminFmt .= "\n