summaryrefslogtreecommitdiff
path: root/inc/auth.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/auth.php')
-rw-r--r--inc/auth.php315
1 files changed, 258 insertions, 57 deletions
diff --git a/inc/auth.php b/inc/auth.php
index 3f1f7925b..36fc7d086 100644
--- a/inc/auth.php
+++ b/inc/auth.php
@@ -40,7 +40,7 @@ function auth_setup() {
global $INPUT;
global $AUTH_ACL;
global $lang;
- global $config_cascade;
+ /* @var Doku_Plugin_Controller $plugin_controller */
global $plugin_controller;
$AUTH_ACL = array();
@@ -48,29 +48,29 @@ function auth_setup() {
// try to load auth backend from plugins
foreach ($plugin_controller->getList('auth') as $plugin) {
- if ($conf['authtype'] === $plugin) {
- $auth = $plugin_controller->load('auth', $plugin);
- break;
- } elseif ('auth' . $conf['authtype'] === $plugin) {
- // matches old auth backends (pre-Weatherwax)
- $auth = $plugin_controller->load('auth', $plugin);
- msg('Your authtype setting is deprecated. You must set $conf[\'authtype\'] = ' . "auth" . $conf['authtype']
- . ' in your configuration (see <a href="https://www.dokuwiki.org/auth">Authentication Backends</a>)',-1,'','',MSG_ADMINS_ONLY);
- }
+ if ($conf['authtype'] === $plugin) {
+ $auth = $plugin_controller->load('auth', $plugin);
+ break;
+ } elseif ('auth' . $conf['authtype'] === $plugin) {
+ // matches old auth backends (pre-Weatherwax)
+ $auth = $plugin_controller->load('auth', $plugin);
+ msg('Your authtype setting is deprecated. You must set $conf[\'authtype\'] = "auth' . $conf['authtype'] . '"'
+ . ' in your configuration (see <a href="https://www.dokuwiki.org/auth">Authentication Backends</a>)',-1,'','',MSG_ADMINS_ONLY);
+ }
}
- if(!isset($auth) || !$auth){
+ if(!isset($auth) || !$auth){
msg($lang['authtempfail'], -1);
return false;
}
if ($auth->success == false) {
- // degrade to unauthenticated user
- unset($auth);
- auth_logoff();
- msg($lang['authtempfail'], -1);
+ // degrade to unauthenticated user
+ unset($auth);
+ auth_logoff();
+ msg($lang['authtempfail'], -1);
return false;
- }
+ }
// do the login either by cookie or provided credentials XXX
$INPUT->set('http_credentials', false);
@@ -136,22 +136,30 @@ function auth_loadACL() {
$acl = file($config_cascade['acl']['default']);
- //support user wildcard
$out = array();
foreach($acl as $line) {
$line = trim($line);
if($line{0} == '#') continue;
list($id,$rest) = preg_split('/\s+/',$line,2);
+ // substitue user wildcard first (its 1:1)
+ if(strstr($line, '%USER%')){
+ // if user is not logged in, this ACL line is meaningless - skip it
+ if (!isset($_SERVER['REMOTE_USER'])) continue;
+
+ $id = str_replace('%USER%',cleanID($_SERVER['REMOTE_USER']),$id);
+ $rest = str_replace('%USER%',auth_nameencode($_SERVER['REMOTE_USER']),$rest);
+ }
+
+ // substitute group wildcard (its 1:m)
if(strstr($line, '%GROUP%')){
+ // if user is not logged in, grps is empty, no output will be added (i.e. skipped)
foreach((array) $USERINFO['grps'] as $grp){
$nid = str_replace('%GROUP%',cleanID($grp),$id);
$nrest = str_replace('%GROUP%','@'.auth_nameencode($grp),$rest);
$out[] = "$nid\t$nrest";
}
} else {
- $id = str_replace('%USER%',cleanID($_SERVER['REMOTE_USER']),$id);
- $rest = str_replace('%USER%',auth_nameencode($_SERVER['REMOTE_USER']),$rest);
$out[] = "$id\t$rest";
}
}
@@ -207,7 +215,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) {
global $USERINFO;
global $conf;
global $lang;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
$sticky ? $sticky = true : $sticky = false; //sanity check
@@ -219,8 +227,8 @@ function auth_login($user, $pass, $sticky = false, $silent = false) {
if($auth->checkPass($user, $pass)) {
// make logininfo globally available
$_SERVER['REMOTE_USER'] = $user;
- $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session
- auth_setCookie($user, PMA_blowfish_encrypt($pass, $secret), $sticky);
+ $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session
+ auth_setCookie($user, auth_encrypt($pass, $secret), $sticky);
return true;
} else {
//invalid credentials - log off
@@ -250,8 +258,8 @@ function auth_login($user, $pass, $sticky = false, $silent = false) {
return true;
}
// no we don't trust it yet - recheck pass but silent
- $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session
- $pass = PMA_blowfish_decrypt($pass, $secret);
+ $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session
+ $pass = auth_decrypt($pass, $secret);
return auth_login($user, $pass, $sticky, true);
}
}
@@ -294,7 +302,7 @@ function auth_validateToken($token) {
* @return string The auth token
*/
function auth_createToken() {
- $token = md5(mt_rand());
+ $token = md5(auth_randombytes(16));
@session_start(); // reopen the session if needed
$_SESSION[DOKU_COOKIE]['auth']['token'] = $token;
session_write_close();
@@ -333,14 +341,18 @@ function auth_browseruid() {
*
* @author Andreas Gohr <andi@splitbrain.org>
* @param bool $addsession if true, the sessionid is added to the salt
+ * @param bool $secure if security is more important than keeping the old value
* @return string
*/
-function auth_cookiesalt($addsession = false) {
+function auth_cookiesalt($addsession = false, $secure = false) {
global $conf;
$file = $conf['metadir'].'/_htcookiesalt';
+ if ($secure || !file_exists($file)) {
+ $file = $conf['metadir'].'/_htcookiesalt2';
+ }
$salt = io_readFile($file);
if(empty($salt)) {
- $salt = uniqid(rand(), true);
+ $salt = bin2hex(auth_randombytes(64));
io_saveFile($file, $salt);
}
if($addsession) {
@@ -350,6 +362,142 @@ function auth_cookiesalt($addsession = false) {
}
/**
+ * Return truly (pseudo) random bytes if available, otherwise fall back to mt_rand
+ *
+ * @author Mark Seecof
+ * @author Michael Hamann <michael@content-space.de>
+ * @link http://www.php.net/manual/de/function.mt-rand.php#83655
+ * @param int $length number of bytes to get
+ * @return string binary random strings
+ */
+function auth_randombytes($length) {
+ $strong = false;
+ $rbytes = false;
+
+ if (function_exists('openssl_random_pseudo_bytes')
+ && (version_compare(PHP_VERSION, '5.3.4') >= 0
+ || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')
+ ) {
+ $rbytes = openssl_random_pseudo_bytes($length, $strong);
+ }
+
+ if (!$strong && function_exists('mcrypt_create_iv')
+ && (version_compare(PHP_VERSION, '5.3.7') >= 0
+ || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')
+ ) {
+ $rbytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
+ if ($rbytes !== false && strlen($rbytes) === $length) {
+ $strong = true;
+ }
+ }
+
+ // If no strong randoms available, try OS the specific ways
+ if(!$strong) {
+ // Unix/Linux platform
+ $fp = @fopen('/dev/urandom', 'rb');
+ if($fp !== false) {
+ $rbytes = fread($fp, $length);
+ fclose($fp);
+ }
+
+ // MS-Windows platform
+ if(class_exists('COM')) {
+ // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
+ try {
+ $CAPI_Util = new COM('CAPICOM.Utilities.1');
+ $rbytes = $CAPI_Util->GetRandom($length, 0);
+
+ // if we ask for binary data PHP munges it, so we
+ // request base64 return value.
+ if($rbytes) $rbytes = base64_decode($rbytes);
+ } catch(Exception $ex) {
+ // fail
+ }
+ }
+ }
+ if(strlen($rbytes) < $length) $rbytes = false;
+
+ // still no random bytes available - fall back to mt_rand()
+ if($rbytes === false) {
+ $rbytes = '';
+ for ($i = 0; $i < $length; ++$i) {
+ $rbytes .= chr(mt_rand(0, 255));
+ }
+ }
+
+ return $rbytes;
+}
+
+/**
+ * Random number generator using the best available source
+ *
+ * @author Michael Samuel
+ * @author Michael Hamann <michael@content-space.de>
+ * @param int $min
+ * @param int $max
+ * @return int
+ */
+function auth_random($min, $max) {
+ $abs_max = $max - $min;
+
+ $nbits = 0;
+ for ($n = $abs_max; $n > 0; $n >>= 1) {
+ ++$nbits;
+ }
+
+ $mask = (1 << $nbits) - 1;
+ do {
+ $bytes = auth_randombytes(PHP_INT_SIZE);
+ $integers = unpack('Inum', $bytes);
+ $integer = $integers["num"] & $mask;
+ } while ($integer > $abs_max);
+
+ return $min + $integer;
+}
+
+/**
+ * Encrypt data using the given secret using AES
+ *
+ * The mode is CBC with a random initialization vector, the key is derived
+ * using pbkdf2.
+ *
+ * @param string $data The data that shall be encrypted
+ * @param string $secret The secret/password that shall be used
+ * @return string The ciphertext
+ */
+function auth_encrypt($data, $secret) {
+ $iv = auth_randombytes(16);
+ $cipher = new Crypt_AES();
+ $cipher->setPassword($secret);
+
+ /*
+ this uses the encrypted IV as IV as suggested in
+ http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, Appendix C
+ for unique but necessarily random IVs. The resulting ciphertext is
+ compatible to ciphertext that was created using a "normal" IV.
+ */
+ return $cipher->encrypt($iv.$data);
+}
+
+/**
+ * Decrypt the given AES ciphertext
+ *
+ * The mode is CBC, the key is derived using pbkdf2
+ *
+ * @param string $ciphertext The encrypted data
+ * @param string $secret The secret/password that shall be used
+ * @return string The decrypted data
+ */
+function auth_decrypt($ciphertext, $secret) {
+ $iv = substr($ciphertext, 0, 16);
+ $cipher = new Crypt_AES();
+ $cipher->setPassword($secret);
+ $cipher->setIV($iv);
+
+ return $cipher->decrypt(substr($ciphertext, 16));
+}
+
+/**
* Log out the current user
*
* This clears all authentication data and thus log the user
@@ -361,7 +509,7 @@ function auth_cookiesalt($addsession = false) {
function auth_logoff($keepbc = false) {
global $conf;
global $USERINFO;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
// make sure the session is writable (it usually is)
@@ -407,7 +555,7 @@ function auth_logoff($keepbc = false) {
function auth_ismanager($user = null, $groups = null, $adminonly = false) {
global $conf;
global $USERINFO;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
if(!$auth) return false;
@@ -460,7 +608,7 @@ function auth_isadmin($user = null, $groups = null) {
* @return bool true for membership acknowledged
*/
function auth_isMember($memberlist, $user, array $groups) {
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
if(!$auth) return false;
@@ -526,7 +674,7 @@ function auth_quickaclcheck($id) {
function auth_aclcheck($id, $user, $groups) {
global $conf;
global $AUTH_ACL;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
// if no ACL is used always return upload rights
@@ -678,27 +826,41 @@ function auth_nameencode($name, $skip_group = false) {
/**
* Create a pronouncable password
*
- * @author Andreas Gohr <andi@splitbrain.org>
- * @link http://www.phpbuilder.com/annotate/message.php3?id=1014451
+ * The $foruser variable might be used by plugins to run additional password
+ * policy checks, but is not used by the default implementation
*
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @link http://www.phpbuilder.com/annotate/message.php3?id=1014451
+ * @triggers AUTH_PASSWORD_GENERATE
+ *
+ * @param string $foruser username for which the password is generated
* @return string pronouncable password
*/
-function auth_pwgen() {
- $pw = '';
- $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
- $v = 'aeiou'; //vowels
- $a = $c.$v; //both
-
- //use two syllables...
- for($i = 0; $i < 2; $i++) {
- $pw .= $c[rand(0, strlen($c) - 1)];
- $pw .= $v[rand(0, strlen($v) - 1)];
- $pw .= $a[rand(0, strlen($a) - 1)];
+function auth_pwgen($foruser = '') {
+ $data = array(
+ 'password' => '',
+ 'foruser' => $foruser
+ );
+
+ $evt = new Doku_Event('AUTH_PASSWORD_GENERATE', $data);
+ if($evt->advise_before(true)) {
+ $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
+ $v = 'aeiou'; //vowels
+ $a = $c.$v; //both
+ $s = '!$%&?+*~#-_:.;,'; // specials
+
+ //use thre syllables...
+ for($i = 0; $i < 3; $i++) {
+ $data['password'] .= $c[auth_random(0, strlen($c) - 1)];
+ $data['password'] .= $v[auth_random(0, strlen($v) - 1)];
+ $data['password'] .= $a[auth_random(0, strlen($a) - 1)];
+ }
+ //... and add a nice number and special
+ $data['password'] .= auth_random(10, 99).$s[auth_random(0, strlen($s) - 1)];
}
- //... and add a nice number
- $pw .= rand(10, 99);
+ $evt->advise_after();
- return $pw;
+ return $data['password'];
}
/**
@@ -711,7 +873,7 @@ function auth_pwgen() {
*/
function auth_sendPassword($user, $password) {
global $lang;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
if(!$auth) return false;
@@ -745,7 +907,7 @@ function auth_sendPassword($user, $password) {
function register() {
global $lang;
global $conf;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
global $INPUT;
@@ -765,7 +927,7 @@ function register() {
}
if($conf['autopasswd']) {
- $pass = auth_pwgen(); // automatically generate password
+ $pass = auth_pwgen($login); // automatically generate password
} elseif(empty($pass) || empty($passchk)) {
msg($lang['regmissing'], -1); // complain about missing passwords
return false;
@@ -814,7 +976,7 @@ function register() {
function updateprofile() {
global $conf;
global $lang;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
/* @var Input $INPUT */
global $INPUT;
@@ -869,7 +1031,7 @@ function updateprofile() {
if($conf['profileconfirm']) {
if(!$auth->checkPass($_SERVER['REMOTE_USER'], $INPUT->post->str('oldpass'))) {
- msg($lang['badlogin'], -1);
+ msg($lang['badpassconfirm'], -1);
return false;
}
}
@@ -878,7 +1040,7 @@ function updateprofile() {
// update cookie and session with the changed data
if($changes['pass']) {
list( /*user*/, $sticky, /*pass*/) = auth_getCookie();
- $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky));
+ $pass = auth_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true));
auth_setCookie($_SERVER['REMOTE_USER'], $pass, (bool) $sticky);
}
return true;
@@ -887,6 +1049,45 @@ function updateprofile() {
return false;
}
+function auth_deleteprofile(){
+ global $conf;
+ global $lang;
+ /* @var DokuWiki_Auth_Plugin $auth */
+ global $auth;
+ /* @var Input $INPUT */
+ global $INPUT;
+
+ if(!$INPUT->post->bool('delete')) return false;
+ if(!checkSecurityToken()) return false;
+
+ // action prevented or auth module disallows
+ if(!actionOK('profile_delete') || !$auth->canDo('delUser')) {
+ msg($lang['profnodelete'], -1);
+ return false;
+ }
+
+ if(!$INPUT->post->bool('confirm_delete')){
+ msg($lang['profconfdeletemissing'], -1);
+ return false;
+ }
+
+ if($conf['profileconfirm']) {
+ if(!$auth->checkPass($_SERVER['REMOTE_USER'], $INPUT->post->str('oldpass'))) {
+ msg($lang['badpassconfirm'], -1);
+ return false;
+ }
+ }
+
+ $deleted[] = $_SERVER['REMOTE_USER'];
+ if($auth->triggerUserMod('delete', array($deleted))) {
+ // force and immediate logout including removing the sticky cookie
+ auth_logoff();
+ return true;
+ }
+
+ return false;
+}
+
/**
* Send a new password
*
@@ -904,7 +1105,7 @@ function updateprofile() {
function act_resendpwd() {
global $lang;
global $conf;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
/* @var Input $INPUT */
global $INPUT;
@@ -958,7 +1159,7 @@ function act_resendpwd() {
} else { // autogenerate the password and send by mail
- $pass = auth_pwgen();
+ $pass = auth_pwgen($user);
if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
msg('error modifying user data', -1);
return false;
@@ -993,7 +1194,7 @@ function act_resendpwd() {
}
// generate auth token
- $token = md5(auth_cookiesalt().$user); //secret but user based
+ $token = md5(auth_randombytes(16)); // random secret
$tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
$url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&');
@@ -1070,7 +1271,7 @@ function auth_verifyPassword($clear, $crypt) {
*/
function auth_setCookie($user, $pass, $sticky) {
global $conf;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
global $USERINFO;