summaryrefslogtreecommitdiff
path: root/inc/auth.php
diff options
context:
space:
mode:
authorAndreas Gohr <andi@splitbrain.org>2013-10-28 20:22:05 +0100
committerAndreas Gohr <andi@splitbrain.org>2013-10-28 20:22:05 +0100
commit23a363f01514464eb2238ac09ec7723d03d57ecb (patch)
tree42c7dcff8c5b0e506a25d08ee5583ed2a24e8602 /inc/auth.php
parent25e48e54df60b3df6efa365daceb3a8966c8f427 (diff)
parent75cf672f10a71f7dee6d50eb70b742689838bc36 (diff)
downloadrpg-23a363f01514464eb2238ac09ec7723d03d57ecb.tar.gz
rpg-23a363f01514464eb2238ac09ec7723d03d57ecb.tar.bz2
Merge branch 'master' into stable
* master: (413 commits) release preparation fixed strict violation in ACL plugin Fix issues from teams:i18n:translation-check in localizations ensure locale is set back to the original value skip FS#2867 test if \s doesn't match \xA0 after attempting to change the locale unittests for auth_loadACL translation update allow charset for SSO to be configured FS#2148 Mailer: avoid overlong headers in content ids FS#2868 translation update translation update replace \s, \S with [ \t], [^ \t] in regexs used with acls translation update translation update translation update translation update Fix handling of the legacy subscription action name remove obsolete opera handling and session closing remove no longer used ajax.php fix proxy CONNECT where HTTP 1.1 answer is given ...
Diffstat (limited to 'inc/auth.php')
-rw-r--r--inc/auth.php301
1 files changed, 246 insertions, 55 deletions
diff --git a/inc/auth.php b/inc/auth.php
index 6107645cd..b793f5d12 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);
+ if(empty($line) || ($line{0} == '#')) continue; // skip blank lines & comments
+ list($id,$rest) = preg_split('/[ \t]+/',$line,2);
+
+ // substitute 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
@@ -568,11 +716,11 @@ function auth_aclcheck($id, $user, $groups) {
}
//check exact match first
- $matches = preg_grep('/^'.preg_quote($id, '/').'\s+(\S+)\s+/u', $AUTH_ACL);
+ $matches = preg_grep('/^'.preg_quote($id, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL);
if(count($matches)) {
foreach($matches as $match) {
$match = preg_replace('/#.*$/', '', $match); //ignore comments
- $acl = preg_split('/\s+/', $match);
+ $acl = preg_split('/[ \t]+/', $match);
if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') {
$acl[1] = utf8_strtolower($acl[1]);
}
@@ -598,11 +746,11 @@ function auth_aclcheck($id, $user, $groups) {
}
do {
- $matches = preg_grep('/^'.preg_quote($path, '/').'\s+(\S+)\s+/u', $AUTH_ACL);
+ $matches = preg_grep('/^'.preg_quote($path, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL);
if(count($matches)) {
foreach($matches as $match) {
$match = preg_replace('/#.*$/', '', $match); //ignore comments
- $acl = preg_split('/\s+/', $match);
+ $acl = preg_split('/[ \t]+/', $match);
if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') {
$acl[1] = utf8_strtolower($acl[1]);
}
@@ -660,14 +808,14 @@ function auth_nameencode($name, $skip_group = false) {
if(!isset($cache[$name][$skip_group])) {
if($skip_group && $name{0} == '@') {
- $cache[$name][$skip_group] = '@'.preg_replace(
- '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
- "'%'.dechex(ord(substr('\\1',-1)))", substr($name, 1)
+ $cache[$name][$skip_group] = '@'.preg_replace_callback(
+ '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/',
+ 'auth_nameencode_callback', substr($name, 1)
);
} else {
- $cache[$name][$skip_group] = preg_replace(
- '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
- "'%'.dechex(ord(substr('\\1',-1)))", $name
+ $cache[$name][$skip_group] = preg_replace_callback(
+ '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/',
+ 'auth_nameencode_callback', $name
);
}
}
@@ -675,6 +823,10 @@ function auth_nameencode($name, $skip_group = false) {
return $cache[$name][$skip_group];
}
+function auth_nameencode_callback($matches) {
+ return '%'.dechex(ord(substr($matches[1],-1)));
+}
+
/**
* Create a pronouncable password
*
@@ -703,12 +855,12 @@ function auth_pwgen($foruser = '') {
//use thre syllables...
for($i = 0; $i < 3; $i++) {
- $data['password'] .= $c[mt_rand(0, strlen($c) - 1)];
- $data['password'] .= $v[mt_rand(0, strlen($v) - 1)];
- $data['password'] .= $a[mt_rand(0, strlen($a) - 1)];
+ $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'] .= mt_rand(10, 99).$s[mt_rand(0, strlen($s) - 1)];
+ $data['password'] .= auth_random(10, 99).$s[auth_random(0, strlen($s) - 1)];
}
$evt->advise_after();
@@ -725,7 +877,7 @@ function auth_pwgen($foruser = '') {
*/
function auth_sendPassword($user, $password) {
global $lang;
- /* @var auth_basic $auth */
+ /* @var DokuWiki_Auth_Plugin $auth */
global $auth;
if(!$auth) return false;
@@ -759,7 +911,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;
@@ -828,7 +980,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;
@@ -883,7 +1035,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;
}
}
@@ -892,7 +1044,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;
@@ -901,6 +1053,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
*
@@ -918,7 +1109,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;
@@ -1007,7 +1198,7 @@ function act_resendpwd() {
}
// generate auth token
- $token = md5(uniqid(mt_rand(), true)); // random secret
+ $token = md5(auth_randombytes(16)); // random secret
$tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
$url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&');
@@ -1084,7 +1275,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;