summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAndreas Gohr <andi@splitbrain.org>2012-10-06 10:46:10 +0200
committerAndreas Gohr <andi@splitbrain.org>2012-10-06 10:46:10 +0200
commit93a7873eb0646b5712d75b031cd8b8b143968ba2 (patch)
treea97bf3309c75947475a42a73f23727f7d2642677 /lib
parent2ca4ac4d5e2214a945489b6aa66c9542b3ee2e4a (diff)
parentf4476bd9b5badd36cd0617d76538e47d9649986b (diff)
downloadrpg-93a7873eb0646b5712d75b031cd8b8b143968ba2.tar.gz
rpg-93a7873eb0646b5712d75b031cd8b8b143968ba2.tar.bz2
Merge remote-tracking branch 'janschumann/master' into future
This merge fixes all conflicts but is otherwise untested and might break funktionality in the auth system somewhere. It NEEDS MAJOR TESTING! Some refactoring of the auth plugins is still needed: * move to PHP5 style * fix comments * add plugin.info.txt * janschumann/master: Refactored auth system: All auth methods are now introduced as plugins. Bugfix: auth types are now correcty added Setup auth system from plugins Added Auth-Plugin-Prototype to autoload Load auth types from plugins in settings_authtype class Added prototype for Auth-Plugins added plugin type 'auth' Conflicts: inc/auth.php inc/auth/pgsql.class.php inc/init.php inc/load.php lib/plugins/auth.php lib/plugins/authad/auth.php lib/plugins/authldap/auth.php lib/plugins/authmysql/auth.php lib/plugins/authplain/auth.php
Diffstat (limited to 'lib')
-rw-r--r--lib/plugins/auth.php423
-rw-r--r--lib/plugins/authad/auth.php524
-rw-r--r--lib/plugins/authad/plugin.info.txt7
-rw-r--r--lib/plugins/authldap/auth.php487
-rw-r--r--lib/plugins/authldap/plugin.info.txt7
-rw-r--r--lib/plugins/authmysql/auth.php947
-rw-r--r--lib/plugins/authmysql/plugin.info.txt7
-rw-r--r--lib/plugins/authpgsql/auth.php331
-rw-r--r--lib/plugins/authpgsql/plugin.info.txt7
-rw-r--r--lib/plugins/authplain/auth.php328
-rw-r--r--lib/plugins/authplain/plugin.info.txt7
-rw-r--r--lib/plugins/config/settings/extra.class.php51
12 files changed, 3119 insertions, 7 deletions
diff --git a/lib/plugins/auth.php b/lib/plugins/auth.php
new file mode 100644
index 000000000..637435252
--- /dev/null
+++ b/lib/plugins/auth.php
@@ -0,0 +1,423 @@
+<?php
+/**
+ * Auth Plugin Prototype
+ *
+ * foundation authorisation class
+ * all auth classes should inherit from this class
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Jan Schumann <js@jschumann-it.com>
+
+ */
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+class DokuWiki_Auth_Plugin extends DokuWiki_Plugin {
+ var $success = true;
+
+ /**
+ * Posible things an auth backend module may be able to
+ * do. The things a backend can do need to be set to true
+ * in the constructor.
+ */
+ var $cando = array (
+ 'addUser' => false, // can Users be created?
+ 'delUser' => false, // can Users be deleted?
+ 'modLogin' => false, // can login names be changed?
+ 'modPass' => false, // can passwords be changed?
+ 'modName' => false, // can real names be changed?
+ 'modMail' => false, // can emails be changed?
+ 'modGroups' => false, // can groups be changed?
+ 'getUsers' => false, // can a (filtered) list of users be retrieved?
+ 'getUserCount'=> false, // can the number of users be retrieved?
+ 'getGroups' => false, // can a list of available groups be retrieved?
+ 'external' => false, // does the module do external auth checking?
+ 'logout' => true, // can the user logout again? (eg. not possible with HTTP auth)
+ );
+
+ /**
+ * Constructor.
+ *
+ * Carry out sanity checks to ensure the object is
+ * able to operate. Set capabilities in $this->cando
+ * array here
+ *
+ * Set $this->success to false if checks fail
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+ function __construct() {
+ // the base class constructor does nothing, derived class
+ // constructors do the real work
+ }
+
+ /**
+ * Capability check. [ DO NOT OVERRIDE ]
+ *
+ * Checks the capabilities set in the $this->cando array and
+ * some pseudo capabilities (shortcutting access to multiple
+ * ones)
+ *
+ * ususal capabilities start with lowercase letter
+ * shortcut capabilities start with uppercase letter
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool
+ */
+ function canDo($cap) {
+ switch($cap){
+ case 'Profile':
+ // can at least one of the user's properties be changed?
+ return ( $this->cando['modPass'] ||
+ $this->cando['modName'] ||
+ $this->cando['modMail'] );
+ break;
+ case 'UserMod':
+ // can at least anything be changed?
+ return ( $this->cando['modPass'] ||
+ $this->cando['modName'] ||
+ $this->cando['modMail'] ||
+ $this->cando['modLogin'] ||
+ $this->cando['modGroups'] ||
+ $this->cando['modMail'] );
+ break;
+ default:
+ // print a helping message for developers
+ if(!isset($this->cando[$cap])){
+ msg("Check for unknown capability '$cap' - Do you use an outdated Plugin?",-1);
+ }
+ return $this->cando[$cap];
+ }
+ }
+
+ /**
+ * Trigger the AUTH_USERDATA_CHANGE event and call the modification function. [ DO NOT OVERRIDE ]
+ *
+ * You should use this function instead of calling createUser, modifyUser or
+ * deleteUsers directly. The event handlers can prevent the modification, for
+ * example for enforcing a user name schema.
+ *
+ * @author Gabriel Birke <birke@d-scribe.de>
+ * @param string $type Modification type ('create', 'modify', 'delete')
+ * @param array $params Parameters for the createUser, modifyUser or deleteUsers method. The content of this array depends on the modification type
+ * @return mixed Result from the modification function or false if an event handler has canceled the action
+ */
+ function triggerUserMod($type, $params) {
+ $validTypes = array(
+ 'create' => 'createUser',
+ 'modify' => 'modifyUser',
+ 'delete' => 'deleteUsers'
+ );
+ if(empty($validTypes[$type]))
+ return false;
+ $eventdata = array('type' => $type, 'params' => $params, 'modification_result' => null);
+ $evt = new Doku_Event('AUTH_USER_CHANGE', $eventdata);
+ if ($evt->advise_before(true)) {
+ $result = call_user_func_array(array($this, $validTypes[$type]), $params);
+ $evt->data['modification_result'] = $result;
+ }
+ $evt->advise_after();
+ unset($evt);
+ return $result;
+ }
+
+ /**
+ * Log off the current user [ OPTIONAL ]
+ *
+ * Is run in addition to the ususal logoff method. Should
+ * only be needed when trustExternal is implemented.
+ *
+ * @see auth_logoff()
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function logOff(){
+ }
+
+ /**
+ * Do all authentication [ OPTIONAL ]
+ *
+ * Set $this->cando['external'] = true when implemented
+ *
+ * If this function is implemented it will be used to
+ * authenticate a user - all other DokuWiki internals
+ * will not be used for authenticating, thus
+ * implementing the checkPass() function is not needed
+ * anymore.
+ *
+ * The function can be used to authenticate against third
+ * party cookies or Apache auth mechanisms and replaces
+ * the auth_login() function
+ *
+ * The function will be called with or without a set
+ * username. If the Username is given it was called
+ * from the login form and the given credentials might
+ * need to be checked. If no username was given it
+ * the function needs to check if the user is logged in
+ * by other means (cookie, environment).
+ *
+ * The function needs to set some globals needed by
+ * DokuWiki like auth_login() does.
+ *
+ * @see auth_login()
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $user Username
+ * @param string $pass Cleartext Password
+ * @param bool $sticky Cookie should not expire
+ * @return bool true on successful auth
+ */
+ function trustExternal($user,$pass,$sticky=false){
+ /* some example:
+
+ global $USERINFO;
+ global $conf;
+ $sticky ? $sticky = true : $sticky = false; //sanity check
+
+ // do the checking here
+
+ // set the globals if authed
+ $USERINFO['name'] = 'FIXME';
+ $USERINFO['mail'] = 'FIXME';
+ $USERINFO['grps'] = array('FIXME');
+ $_SERVER['REMOTE_USER'] = $user;
+ $_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
+ $_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass;
+ $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
+ return true;
+
+ */
+ }
+
+ /**
+ * Check user+password [ MUST BE OVERRIDDEN ]
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct
+ *
+ * May be ommited if trustExternal is used.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool
+ */
+ function checkPass($user,$pass){
+ msg("no valid authorisation system in use", -1);
+ return false;
+ }
+
+ /**
+ * Return user info [ MUST BE OVERRIDDEN ]
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return array containing user data or false
+ */
+ function getUserData($user) {
+ if(!$this->cando['external']) msg("no valid authorisation system in use", -1);
+ return false;
+ }
+
+ /**
+ * Create a new User [implement only where required/possible]
+ *
+ * Returns false if the user already exists, null when an error
+ * occurred and true if everything went well.
+ *
+ * The new user HAS TO be added to the default group by this
+ * function!
+ *
+ * Set addUser capability when implemented
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function createUser($user,$pass,$name,$mail,$grps=null){
+ msg("authorisation method does not allow creation of new users", -1);
+ return null;
+ }
+
+ /**
+ * Modify user data [implement only where required/possible]
+ *
+ * Set the mod* capabilities according to the implemented features
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param $user nick of the user to be changed
+ * @param $changes array of field/value pairs to be changed (password will be clear text)
+ * @return bool
+ */
+ function modifyUser($user, $changes) {
+ msg("authorisation method does not allow modifying of user data", -1);
+ return false;
+ }
+
+ /**
+ * Delete one or more users [implement only where required/possible]
+ *
+ * Set delUser capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param array $users
+ * @return int number of users deleted
+ */
+ function deleteUsers($users) {
+ msg("authorisation method does not allow deleting of users", -1);
+ return false;
+ }
+
+ /**
+ * Return a count of the number of user which meet $filter criteria
+ * [should be implemented whenever retrieveUsers is implemented]
+ *
+ * Set getUserCount capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+ function getUserCount($filter=array()) {
+ msg("authorisation method does not provide user counts", -1);
+ return 0;
+ }
+
+ /**
+ * Bulk retrieval of user data [implement only where required/possible]
+ *
+ * Set getUsers capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param start index of first user to be returned
+ * @param limit max number of users to be returned
+ * @param filter array of field/pattern pairs, null for no filter
+ * @return array of userinfo (refer getUserData for internal userinfo details)
+ */
+ function retrieveUsers($start=0,$limit=-1,$filter=null) {
+ msg("authorisation method does not support mass retrieval of user data", -1);
+ return array();
+ }
+
+ /**
+ * Define a group [implement only where required/possible]
+ *
+ * Set addGroup capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @return bool
+ */
+ function addGroup($group) {
+ msg("authorisation method does not support independent group creation", -1);
+ return false;
+ }
+
+ /**
+ * Retrieve groups [implement only where required/possible]
+ *
+ * Set getGroups capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @return array
+ */
+ function retrieveGroups($start=0,$limit=0) {
+ msg("authorisation method does not support group list retrieval", -1);
+ return array();
+ }
+
+ /**
+ * Return case sensitivity of the backend [OPTIONAL]
+ *
+ * When your backend is caseinsensitive (eg. you can login with USER and
+ * user) then you need to overwrite this method and return false
+ */
+ function isCaseSensitive(){
+ return true;
+ }
+
+ /**
+ * Sanitize a given username [OPTIONAL]
+ *
+ * This function is applied to any user name that is given to
+ * the backend and should also be applied to any user name within
+ * the backend before returning it somewhere.
+ *
+ * This should be used to enforce username restrictions.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $user - username
+ * @param string - the cleaned username
+ */
+ function cleanUser($user){
+ return $user;
+ }
+
+ /**
+ * Sanitize a given groupname [OPTIONAL]
+ *
+ * This function is applied to any groupname that is given to
+ * the backend and should also be applied to any groupname within
+ * the backend before returning it somewhere.
+ *
+ * This should be used to enforce groupname restrictions.
+ *
+ * Groupnames are to be passed without a leading '@' here.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $group - groupname
+ * @param string - the cleaned groupname
+ */
+ function cleanGroup($group){
+ return $group;
+ }
+
+
+ /**
+ * Check Session Cache validity [implement only where required/possible]
+ *
+ * DokuWiki caches user info in the user's session for the timespan defined
+ * in $conf['auth_security_timeout'].
+ *
+ * This makes sure slow authentication backends do not slow down DokuWiki.
+ * This also means that changes to the user database will not be reflected
+ * on currently logged in users.
+ *
+ * To accommodate for this, the user manager plugin will touch a reference
+ * file whenever a change is submitted. This function compares the filetime
+ * of this reference file with the time stored in the session.
+ *
+ * This reference file mechanism does not reflect changes done directly in
+ * the backend's database through other means than the user manager plugin.
+ *
+ * Fast backends might want to return always false, to force rechecks on
+ * each page load. Others might want to use their own checking here. If
+ * unsure, do not override.
+ *
+ * @param string $user - The username
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool
+ */
+ function useSessionCache($user){
+ global $conf;
+ return ($_SESSION[DOKU_COOKIE]['auth']['time'] >= @filemtime($conf['cachedir'].'/sessionpurge'));
+ }
+
+
+ /**
+ * loadConfig()
+ * merges the plugin's default settings with any local settings
+ * this function is automatically called through getConf()
+ */
+ function loadConfig(){
+ global $conf;
+
+ parent::loadConfig();
+
+ $this->conf['debug'] = $conf['debug'];
+ $this->conf['useacl'] = $conf['useacl'];
+ $this->conf['disableactions'] = $conf['disableactions'];
+ $this->conf['autopasswd'] = $conf['autopasswd'];
+ $this->conf['passcrypt'] = $conf['ssha'];
+ }
+
+}
diff --git a/lib/plugins/authad/auth.php b/lib/plugins/authad/auth.php
new file mode 100644
index 000000000..35c19f471
--- /dev/null
+++ b/lib/plugins/authad/auth.php
@@ -0,0 +1,524 @@
+<?php
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+require_once(DOKU_INC.'inc/adLDAP.php');
+
+/**
+ * Active Directory authentication backend for DokuWiki
+ *
+ * This makes authentication with a Active Directory server much easier
+ * than when using the normal LDAP backend by utilizing the adLDAP library
+ *
+ * Usage:
+ * Set DokuWiki's local.protected.php auth setting to read
+ *
+ * $conf['useacl'] = 1;
+ * $conf['disableactions'] = 'register';
+ * $conf['autopasswd'] = 0;
+ * $conf['authtype'] = 'authad';
+ * $conf['passcrypt'] = 'ssha';
+ *
+ * $conf['auth']['ad']['account_suffix'] = '
+ *
+ * @my.domain.org';
+ * $conf['auth']['ad']['base_dn'] = 'DC=my,DC=domain,DC=org';
+ * $conf['auth']['ad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
+ *
+ * //optional:
+ * $conf['auth']['ad']['sso'] = 1;
+ * $conf['auth']['ad']['ad_username'] = 'root';
+ * $conf['auth']['ad']['ad_password'] = 'pass';
+ * $conf['auth']['ad']['real_primarygroup'] = 1;
+ * $conf['auth']['ad']['use_ssl'] = 1;
+ * $conf['auth']['ad']['use_tls'] = 1;
+ * $conf['auth']['ad']['debug'] = 1;
+ * // warn user about expiring password this many days in advance:
+ * $conf['auth']['ad']['expirywarn'] = 5;
+ *
+ * // get additional information to the userinfo array
+ * // add a list of comma separated ldap contact fields.
+ * $conf['plugin']['authad']['additional'] = 'field1,field2';
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author James Van Lommel <jamesvl@gmail.com>
+ * @link http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+
+require_once(DOKU_INC.'inc/adLDAP/adLDAP.php');
+
+class auth_plugin_authad extends DokuWiki_Auth_Plugin {
+ /**
+ * @var array copy of the auth backend configuration
+ */
+ protected $cnf = array();
+ /**
+ * @var array hold connection data for a specific AD domain
+ */
+ protected $opts = array();
+ /**
+ * @var array open connections for each AD domain, as adLDAP objects
+ */
+ protected $adldap = array();
+
+ /**
+ * @var bool message state
+ */
+ protected $msgshown = false;
+
+ /**
+ * @var array user listing cache
+ */
+ protected $users = array();
+
+ /**
+ * @var array filter patterns for listing users
+ */
+ protected $_pattern = array();
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ global $conf;
+ $this->cnf = $conf['auth']['ad'];
+
+ // additional information fields
+ if(isset($this->cnf['additional'])) {
+ $this->cnf['additional'] = str_replace(' ', '', $this->cnf['additional']);
+ $this->cnf['additional'] = explode(',', $this->cnf['additional']);
+ } else $this->cnf['additional'] = array();
+
+ // ldap extension is needed
+ if(!function_exists('ldap_connect')) {
+ if($this->cnf['debug'])
+ msg("AD Auth: PHP LDAP extension not found.", -1);
+ $this->success = false;
+ return;
+ }
+
+ // Prepare SSO
+ if(!utf8_check($_SERVER['REMOTE_USER'])) {
+ $_SERVER['REMOTE_USER'] = utf8_encode($_SERVER['REMOTE_USER']);
+ }
+ if($_SERVER['REMOTE_USER'] && $this->cnf['sso']) {
+ $_SERVER['REMOTE_USER'] = $this->cleanUser($_SERVER['REMOTE_USER']);
+
+ // we need to simulate a login
+ if(empty($_COOKIE[DOKU_COOKIE])) {
+ $_REQUEST['u'] = $_SERVER['REMOTE_USER'];
+ $_REQUEST['p'] = 'sso_only';
+ }
+ }
+
+ // other can do's are changed in $this->_loadServerConfig() base on domain setup
+ $this->cando['modName'] = true;
+ $this->cando['modMail'] = true;
+ }
+
+ /**
+ * Check user+password [required auth function]
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct by trying to bind
+ * to the LDAP server
+ *
+ * @author James Van Lommel <james@nosq.com>
+ * @param string $user
+ * @param string $pass
+ * @return bool
+ */
+ public function checkPass($user, $pass) {
+ if($_SERVER['REMOTE_USER'] &&
+ $_SERVER['REMOTE_USER'] == $user &&
+ $this->cnf['sso']
+ ) return true;
+
+ $adldap = $this->_adldap($this->_userDomain($user));
+ if(!$adldap) return false;
+
+ return $adldap->authenticate($this->_userName($user), $pass);
+ }
+
+ /**
+ * Return user info [required auth function]
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email address of the user
+ * grps array list of groups the user is in
+ *
+ * This AD specific function returns the following
+ * addional fields:
+ *
+ * dn string distinguished name (DN)
+ * uid string samaccountname
+ * lastpwd int timestamp of the date when the password was set
+ * expires true if the password expires
+ * expiresin int seconds until the password expires
+ * any fields specified in the 'additional' config option
+ *
+ * @author James Van Lommel <james@nosq.com>
+ * @param string $user
+ * @return array
+ */
+ public function getUserData($user) {
+ global $conf;
+ global $lang;
+ global $ID;
+ $adldap = $this->_adldap($this->_userDomain($user));
+ if(!$adldap) return false;
+
+ if($user == '') return array();
+
+ $fields = array('mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol');
+
+ // add additional fields to read
+ $fields = array_merge($fields, $this->cnf['additional']);
+ $fields = array_unique($fields);
+
+ //get info for given user
+ $result = $this->adldap->user()->info($this->_userName($user), $fields);
+ if($result == false){
+ return array();
+ }
+
+ //general user info
+ $info['name'] = $result[0]['displayname'][0];
+ $info['mail'] = $result[0]['mail'][0];
+ $info['uid'] = $result[0]['samaccountname'][0];
+ $info['dn'] = $result[0]['dn'];
+ //last password set (Windows counts from January 1st 1601)
+ $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10000000 - 11644473600;
+ //will it expire?
+ $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD
+
+ // additional information
+ foreach($this->cnf['additional'] as $field) {
+ if(isset($result[0][strtolower($field)])) {
+ $info[$field] = $result[0][strtolower($field)][0];
+ }
+ }
+
+ // handle ActiveDirectory memberOf
+ $info['grps'] = $this->adldap->user()->groups($this->_userName($user),(bool) $this->opts['recursive_groups']);
+
+ if(is_array($info['grps'])) {
+ foreach($info['grps'] as $ndx => $group) {
+ $info['grps'][$ndx] = $this->cleanGroup($group);
+ }
+ }
+
+ // always add the default group to the list of groups
+ if(!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) {
+ $info['grps'][] = $conf['defaultgroup'];
+ }
+
+ // add the user's domain to the groups
+ $domain = $this->_userDomain($user);
+ if($domain && !in_array("domain-$domain", (array) $info['grps'])) {
+ $info['grps'][] = $this->cleanGroup("domain-$domain");
+ }
+
+ // check expiry time
+ if($info['expires'] && $this->cnf['expirywarn']){
+ $timeleft = $this->adldap->user()->passwordExpiry($user); // returns unixtime
+ $timeleft = round($timeleft/(24*60*60));
+ $info['expiresin'] = $timeleft;
+
+ // if this is the current user, warn him (once per request only)
+ if(($_SERVER['REMOTE_USER'] == $user) &&
+ ($timeleft <= $this->cnf['expirywarn']) &&
+ !$this->msgshown
+ ) {
+ $msg = sprintf($lang['authpwdexpire'], $timeleft);
+ if($this->canDo('modPass')) {
+ $url = wl($ID, array('do'=> 'profile'));
+ $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>';
+ }
+ msg($msg);
+ $this->msgshown = true;
+ }
+ }
+
+ return $info;
+ }
+
+ /**
+ * Make AD group names usable by DokuWiki.
+ *
+ * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
+ *
+ * @author James Van Lommel (jamesvl@gmail.com)
+ * @param string $group
+ * @return string
+ */
+ public function cleanGroup($group) {
+ $group = str_replace('\\', '', $group);
+ $group = str_replace('#', '', $group);
+ $group = preg_replace('[\s]', '_', $group);
+ $group = utf8_strtolower(trim($group));
+ return $group;
+ }
+
+ /**
+ * Sanitize user names
+ *
+ * Normalizes domain parts, does not modify the user name itself (unlike cleanGroup)
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ * @param string $user
+ * @return string
+ */
+ public function cleanUser($user) {
+ $domain = '';
+
+ // get NTLM or Kerberos domain part
+ list($dom, $user) = explode('\\', $user, 2);
+ if(!$user) $user = $dom;
+ if($dom) $domain = $dom;
+ list($user, $dom) = explode('@', $user, 2);
+ if($dom) $domain = $dom;
+
+ // clean up both
+ $domain = utf8_strtolower(trim($domain));
+ $user = utf8_strtolower(trim($user));
+
+ // is this a known, valid domain? if not discard
+ if(!is_array($this->cnf[$domain])) {
+ $domain = '';
+ }
+
+ // reattach domain
+ if($domain) $user = "$user@$domain";
+ return $user;
+ }
+
+ /**
+ * Most values in LDAP are case-insensitive
+ *
+ * @return bool
+ */
+ public function isCaseSensitive() {
+ return false;
+ }
+
+ /**
+ * Bulk retrieval of user data
+ *
+ * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
+ * @param int $start index of first user to be returned
+ * @param int $limit max number of users to be returned
+ * @param array $filter array of field/pattern pairs, null for no filter
+ * @return array userinfo (refer getUserData for internal userinfo details)
+ */
+ public function retrieveUsers($start = 0, $limit = -1, $filter = array()) {
+ $adldap = $this->_adldap(null);
+ if(!$adldap) return false;
+
+ if($this->users === null) {
+ //get info for given user
+ $result = $this->adldap->user()->all();
+ if (!$result) return array();
+ $this->users = array_fill_keys($result, false);
+ }
+
+ $i = 0;
+ $count = 0;
+ $this->_constructPattern($filter);
+ $result = array();
+
+ foreach($this->users as $user => &$info) {
+ if($i++ < $start) {
+ continue;
+ }
+ if($info === false) {
+ $info = $this->getUserData($user);
+ }
+ if($this->_filter($user, $info)) {
+ $result[$user] = $info;
+ if(($limit >= 0) && (++$count >= $limit)) break;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Modify user data
+ *
+ * @param string $user nick of the user to be changed
+ * @param array $changes array of field/value pairs to be changed
+ * @return bool
+ */
+ public function modifyUser($user, $changes) {
+ $return = true;
+ $adldap = $this->_adldap($this->_userDomain($user));
+ if(!$adldap) return false;
+
+ // password changing
+ if(isset($changes['pass'])) {
+ try {
+ $return = $this->adldap->user()->password($this->_userName($user),$changes['pass']);
+ } catch (adLDAPException $e) {
+ if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
+ $return = false;
+ }
+ if(!$return) msg('AD Auth: failed to change the password. Maybe the password policy was not met?', -1);
+ }
+
+ // changing user data
+ $adchanges = array();
+ if(isset($changes['name'])) {
+ // get first and last name
+ $parts = explode(' ', $changes['name']);
+ $adchanges['surname'] = array_pop($parts);
+ $adchanges['firstname'] = join(' ', $parts);
+ $adchanges['display_name'] = $changes['name'];
+ }
+ if(isset($changes['mail'])) {
+ $adchanges['email'] = $changes['mail'];
+ }
+ if(count($adchanges)) {
+ try {
+ $return = $return & $this->adldap->user()->modify($this->_userName($user),$adchanges);
+ } catch (adLDAPException $e) {
+ if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
+ $return = false;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Initialize the AdLDAP library and connect to the server
+ *
+ * When you pass null as domain, it will reuse any existing domain.
+ * Eg. the one of the logged in user. It falls back to the default
+ * domain if no current one is available.
+ *
+ * @param string|null $domain The AD domain to use
+ * @return adLDAP|bool true if a connection was established
+ */
+ protected function _adldap($domain) {
+ if(is_null($domain) && is_array($this->opts)) {
+ $domain = $this->opts['domain'];
+ }
+
+ $this->opts = $this->_loadServerConfig((string) $domain);
+ if(isset($this->adldap[$domain])) return $this->adldap[$domain];
+
+ // connect
+ try {
+ $this->adldap[$domain] = new adLDAP($this->opts);
+ return $this->adldap[$domain];
+ } catch(adLDAPException $e) {
+ if($this->cnf['debug']) {
+ msg('AD Auth: '.$e->getMessage(), -1);
+ }
+ $this->success = false;
+ $this->adldap[$domain] = null;
+ }
+ return false;
+ }
+
+ /**
+ * Get the domain part from a user
+ *
+ * @param $user
+ * @return string
+ */
+ public function _userDomain($user) {
+ list(, $domain) = explode('@', $user, 2);
+ return $domain;
+ }
+
+ /**
+ * Get the user part from a user
+ *
+ * @param $user
+ * @return string
+ */
+ public function _userName($user) {
+ list($name) = explode('@', $user, 2);
+ return $name;
+ }
+
+ /**
+ * Fetch the configuration for the given AD domain
+ *
+ * @param string $domain current AD domain
+ * @return array
+ */
+ protected function _loadServerConfig($domain) {
+ // prepare adLDAP standard configuration
+ $opts = $this->cnf;
+
+ $opts['domain'] = $domain;
+
+ // add possible domain specific configuration
+ if($domain && is_array($this->cnf[$domain])) foreach($this->cnf[$domain] as $key => $val) {
+ $opts[$key] = $val;
+ }
+
+ // handle multiple AD servers
+ $opts['domain_controllers'] = explode(',', $opts['domain_controllers']);
+ $opts['domain_controllers'] = array_map('trim', $opts['domain_controllers']);
+ $opts['domain_controllers'] = array_filter($opts['domain_controllers']);
+
+ // we can change the password if SSL is set
+ if($opts['use_ssl'] || $opts['use_tls']) {
+ $this->cando['modPass'] = true;
+ } else {
+ $this->cando['modPass'] = false;
+ }
+
+ if(isset($opts['ad_username']) && isset($opts['ad_password'])) {
+ $this->cando['getUsers'] = true;
+ } else {
+ $this->cando['getUsers'] = true;
+ }
+
+ return $opts;
+ }
+
+ /**
+ * Check provided user and userinfo for matching patterns
+ *
+ * The patterns are set up with $this->_constructPattern()
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param string $user
+ * @param array $info
+ * @return bool
+ */
+ protected function _filter($user, $info) {
+ foreach($this->_pattern as $item => $pattern) {
+ if($item == 'user') {
+ if(!preg_match($pattern, $user)) return false;
+ } else if($item == 'grps') {
+ if(!count(preg_grep($pattern, $info['grps']))) return false;
+ } else {
+ if(!preg_match($pattern, $info[$item])) return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Create a pattern for $this->_filter()
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param array $filter
+ */
+ protected function _constructPattern($filter) {
+ $this->_pattern = array();
+ foreach($filter as $item => $pattern) {
+ $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
+ }
+ }
+}
diff --git a/lib/plugins/authad/plugin.info.txt b/lib/plugins/authad/plugin.info.txt
new file mode 100644
index 000000000..ad565b853
--- /dev/null
+++ b/lib/plugins/authad/plugin.info.txt
@@ -0,0 +1,7 @@
+base authad
+author
+email
+date
+name active directory auth plugin
+desc Provides authentication against a Microsoft Active Directory
+url
diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php
new file mode 100644
index 000000000..721abb48e
--- /dev/null
+++ b/lib/plugins/authldap/auth.php
@@ -0,0 +1,487 @@
+<?php
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * LDAP authentication backend
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakaic.co.uk>
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+class auth_plugin_authldap extends DokuWiki_Auth_Plugin {
+ var $cnf = null;
+ var $con = null;
+ var $bound = 0; // 0: anonymous, 1: user, 2: superuser
+
+ /**
+ * Constructor
+ */
+ function __construct(){
+ global $conf;
+ $this->cnf = $conf['auth']['ldap'];
+
+ // ldap extension is needed
+ if(!function_exists('ldap_connect')) {
+ if ($this->cnf['debug'])
+ msg("LDAP err: PHP LDAP extension not found.",-1,__LINE__,__FILE__);
+ $this->success = false;
+ return;
+ }
+
+ if(empty($this->cnf['groupkey'])) $this->cnf['groupkey'] = 'cn';
+ if(empty($this->cnf['userscope'])) $this->cnf['userscope'] = 'sub';
+ if(empty($this->cnf['groupscope'])) $this->cnf['groupscope'] = 'sub';
+
+ // auth_ldap currently just handles authentication, so no
+ // capabilities are set
+ }
+
+ /**
+ * Check user+password
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct by trying to bind
+ * to the LDAP server
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool
+ */
+ function checkPass($user,$pass){
+ // reject empty password
+ if(empty($pass)) return false;
+ if(!$this->_openLDAP()) return false;
+
+ // indirect user bind
+ if($this->cnf['binddn'] && $this->cnf['bindpw']){
+ // use superuser credentials
+ if(!@ldap_bind($this->con,$this->cnf['binddn'],$this->cnf['bindpw'])){
+ if($this->cnf['debug'])
+ msg('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ return false;
+ }
+ $this->bound = 2;
+ }else if($this->cnf['binddn'] &&
+ $this->cnf['usertree'] &&
+ $this->cnf['userfilter']) {
+ // special bind string
+ $dn = $this->_makeFilter($this->cnf['binddn'],
+ array('user'=>$user,'server'=>$this->cnf['server']));
+
+ }else if(strpos($this->cnf['usertree'], '%{user}')) {
+ // direct user bind
+ $dn = $this->_makeFilter($this->cnf['usertree'],
+ array('user'=>$user,'server'=>$this->cnf['server']));
+
+ }else{
+ // Anonymous bind
+ if(!@ldap_bind($this->con)){
+ msg("LDAP: can not bind anonymously",-1);
+ if($this->cnf['debug'])
+ msg('LDAP anonymous bind: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ return false;
+ }
+ }
+
+ // Try to bind to with the dn if we have one.
+ if(!empty($dn)) {
+ // User/Password bind
+ if(!@ldap_bind($this->con,$dn,$pass)){
+ if($this->cnf['debug']){
+ msg("LDAP: bind with $dn failed", -1,__LINE__,__FILE__);
+ msg('LDAP user dn bind: '.htmlspecialchars(ldap_error($this->con)),0);
+ }
+ return false;
+ }
+ $this->bound = 1;
+ return true;
+ }else{
+ // See if we can find the user
+ $info = $this->getUserData($user,true);
+ if(empty($info['dn'])) {
+ return false;
+ } else {
+ $dn = $info['dn'];
+ }
+
+ // Try to bind with the dn provided
+ if(!@ldap_bind($this->con,$dn,$pass)){
+ if($this->cnf['debug']){
+ msg("LDAP: bind with $dn failed", -1,__LINE__,__FILE__);
+ msg('LDAP user bind: '.htmlspecialchars(ldap_error($this->con)),0);
+ }
+ return false;
+ }
+ $this->bound = 1;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return user info
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * This LDAP specific function returns the following
+ * addional fields:
+ *
+ * dn string distinguished name (DN)
+ * uid string Posix User ID
+ * inbind bool for internal use - avoid loop in binding
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Trouble
+ * @author Dan Allen <dan.j.allen@gmail.com>
+ * @author <evaldas.auryla@pheur.org>
+ * @author Stephane Chazelas <stephane.chazelas@emerson.com>
+ * @return array containing user data or false
+ */
+ function getUserData($user,$inbind=false) {
+ global $conf;
+ if(!$this->_openLDAP()) return false;
+
+ // force superuser bind if wanted and not bound as superuser yet
+ if($this->cnf['binddn'] && $this->cnf['bindpw'] && $this->bound < 2){
+ // use superuser credentials
+ if(!@ldap_bind($this->con,$this->cnf['binddn'],$this->cnf['bindpw'])){
+ if($this->cnf['debug'])
+ msg('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ return false;
+ }
+ $this->bound = 2;
+ }elseif($this->bound == 0 && !$inbind) {
+ // in some cases getUserData is called outside the authentication workflow
+ // eg. for sending email notification on subscribed pages. This data might not
+ // be accessible anonymously, so we try to rebind the current user here
+ list($loginuser,$loginsticky,$loginpass) = auth_getCookie();
+ if($loginuser && $loginpass){
+ $loginpass = PMA_blowfish_decrypt($loginpass, auth_cookiesalt(!$loginsticky));
+ $this->checkPass($loginuser, $loginpass);
+ }
+ }
+
+ $info['user'] = $user;
+ $info['server'] = $this->cnf['server'];
+
+ //get info for given user
+ $base = $this->_makeFilter($this->cnf['usertree'], $info);
+ if(!empty($this->cnf['userfilter'])) {
+ $filter = $this->_makeFilter($this->cnf['userfilter'], $info);
+ } else {
+ $filter = "(ObjectClass=*)";
+ }
+
+ $sr = $this->_ldapsearch($this->con, $base, $filter, $this->cnf['userscope']);
+ $result = @ldap_get_entries($this->con, $sr);
+ if($this->cnf['debug']){
+ msg('LDAP user search: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ msg('LDAP search at: '.htmlspecialchars($base.' '.$filter),0,__LINE__,__FILE__);
+ }
+
+ // Don't accept more or less than one response
+ if(!is_array($result) || $result['count'] != 1){
+ return false; //user not found
+ }
+
+ $user_result = $result[0];
+ ldap_free_result($sr);
+
+ // general user info
+ $info['dn'] = $user_result['dn'];
+ $info['gid'] = $user_result['gidnumber'][0];
+ $info['mail'] = $user_result['mail'][0];
+ $info['name'] = $user_result['cn'][0];
+ $info['grps'] = array();
+
+ // overwrite if other attribs are specified.
+ if(is_array($this->cnf['mapping'])){
+ foreach($this->cnf['mapping'] as $localkey => $key) {
+ if(is_array($key)) {
+ // use regexp to clean up user_result
+ list($key, $regexp) = each($key);
+ if($user_result[$key]) foreach($user_result[$key] as $grp){
+ if (preg_match($regexp,$grp,$match)) {
+ if($localkey == 'grps') {
+ $info[$localkey][] = $match[1];
+ } else {
+ $info[$localkey] = $match[1];
+ }
+ }
+ }
+ } else {
+ $info[$localkey] = $user_result[$key][0];
+ }
+ }
+ }
+ $user_result = array_merge($info,$user_result);
+
+ //get groups for given user if grouptree is given
+ if ($this->cnf['grouptree'] || $this->cnf['groupfilter']) {
+ $base = $this->_makeFilter($this->cnf['grouptree'], $user_result);
+ $filter = $this->_makeFilter($this->cnf['groupfilter'], $user_result);
+ $sr = $this->_ldapsearch($this->con, $base, $filter, $this->cnf['groupscope'], array($this->cnf['groupkey']));
+ if($this->cnf['debug']){
+ msg('LDAP group search: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ msg('LDAP search at: '.htmlspecialchars($base.' '.$filter),0,__LINE__,__FILE__);
+ }
+ if(!$sr){
+ msg("LDAP: Reading group memberships failed",-1);
+ return false;
+ }
+ $result = ldap_get_entries($this->con, $sr);
+ ldap_free_result($sr);
+
+ if(is_array($result)) foreach($result as $grp){
+ if(!empty($grp[$this->cnf['groupkey']][0])){
+ if($this->cnf['debug'])
+ msg('LDAP usergroup: '.htmlspecialchars($grp[$this->cnf['groupkey']][0]),0,__LINE__,__FILE__);
+ $info['grps'][] = $grp[$this->cnf['groupkey']][0];
+ }
+ }
+ }
+
+ // always add the default group to the list of groups
+ if(!in_array($conf['defaultgroup'],$info['grps'])){
+ $info['grps'][] = $conf['defaultgroup'];
+ }
+ return $info;
+ }
+
+ /**
+ * Most values in LDAP are case-insensitive
+ */
+ function isCaseSensitive(){
+ return false;
+ }
+
+ /**
+ * Bulk retrieval of user data
+ *
+ * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
+ * @param start index of first user to be returned
+ * @param limit max number of users to be returned
+ * @param filter array of field/pattern pairs, null for no filter
+ * @return array of userinfo (refer getUserData for internal userinfo details)
+ */
+ function retrieveUsers($start=0,$limit=-1,$filter=array()) {
+ if(!$this->_openLDAP()) return false;
+
+ if (!isset($this->users)) {
+ // Perform the search and grab all their details
+ if(!empty($this->cnf['userfilter'])) {
+ $all_filter = str_replace('%{user}', '*', $this->cnf['userfilter']);
+ } else {
+ $all_filter = "(ObjectClass=*)";
+ }
+ $sr=ldap_search($this->con,$this->cnf['usertree'],$all_filter);
+ $entries = ldap_get_entries($this->con, $sr);
+ $users_array = array();
+ for ($i=0; $i<$entries["count"]; $i++){
+ array_push($users_array, $entries[$i]["uid"][0]);
+ }
+ asort($users_array);
+ $result = $users_array;
+ if (!$result) return array();
+ $this->users = array_fill_keys($result, false);
+ }
+ $i = 0;
+ $count = 0;
+ $this->_constructPattern($filter);
+ $result = array();
+
+ foreach ($this->users as $user => &$info) {
+ if ($i++ < $start) {
+ continue;
+ }
+ if ($info === false) {
+ $info = $this->getUserData($user);
+ }
+ if ($this->_filter($user, $info)) {
+ $result[$user] = $info;
+ if (($limit >= 0) && (++$count >= $limit)) break;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Make LDAP filter strings.
+ *
+ * Used by auth_getUserData to make the filter
+ * strings for grouptree and groupfilter
+ *
+ * filter string ldap search filter with placeholders
+ * placeholders array array with the placeholders
+ *
+ * @author Troels Liebe Bentsen <tlb@rapanden.dk>
+ * @return string
+ */
+ function _makeFilter($filter, $placeholders) {
+ preg_match_all("/%{([^}]+)/", $filter, $matches, PREG_PATTERN_ORDER);
+ //replace each match
+ foreach ($matches[1] as $match) {
+ //take first element if array
+ if(is_array($placeholders[$match])) {
+ $value = $placeholders[$match][0];
+ } else {
+ $value = $placeholders[$match];
+ }
+ $value = $this->_filterEscape($value);
+ $filter = str_replace('%{'.$match.'}', $value, $filter);
+ }
+ return $filter;
+ }
+
+ /**
+ * return 1 if $user + $info match $filter criteria, 0 otherwise
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+ function _filter($user, $info) {
+ foreach ($this->_pattern as $item => $pattern) {
+ if ($item == 'user') {
+ if (!preg_match($pattern, $user)) return 0;
+ } else if ($item == 'grps') {
+ if (!count(preg_grep($pattern, $info['grps']))) return 0;
+ } else {
+ if (!preg_match($pattern, $info[$item])) return 0;
+ }
+ }
+ return 1;
+ }
+
+ function _constructPattern($filter) {
+ $this->_pattern = array();
+ foreach ($filter as $item => $pattern) {
+ $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i'; // allow regex characters
+ }
+ }
+
+ /**
+ * Escape a string to be used in a LDAP filter
+ *
+ * Ported from Perl's Net::LDAP::Util escape_filter_value
+ *
+ * @author Andreas Gohr
+ */
+ function _filterEscape($string){
+ return preg_replace('/([\x00-\x1F\*\(\)\\\\])/e',
+ '"\\\\\".join("",unpack("H2","$1"))',
+ $string);
+ }
+
+ /**
+ * Opens a connection to the configured LDAP server and sets the wanted
+ * option on the connection
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _openLDAP(){
+ if($this->con) return true; // connection already established
+
+ $this->bound = 0;
+
+ $port = ($this->cnf['port']) ? $this->cnf['port'] : 389;
+ $bound = false;
+ $servers = explode(',', $this->cnf['server']);
+ foreach ($servers as $server) {
+ $server = trim($server);
+ $this->con = @ldap_connect($server, $port);
+ if (!$this->con) {
+ continue;
+ }
+
+ /*
+ * When OpenLDAP 2.x.x is used, ldap_connect() will always return a resource as it does
+ * not actually connect but just initializes the connecting parameters. The actual
+ * connect happens with the next calls to ldap_* funcs, usually with ldap_bind().
+ *
+ * So we should try to bind to server in order to check its availability.
+ */
+
+ //set protocol version and dependend options
+ if($this->cnf['version']){
+ if(!@ldap_set_option($this->con, LDAP_OPT_PROTOCOL_VERSION,
+ $this->cnf['version'])){
+ msg('Setting LDAP Protocol version '.$this->cnf['version'].' failed',-1);
+ if($this->cnf['debug'])
+ msg('LDAP version set: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ }else{
+ //use TLS (needs version 3)
+ if($this->cnf['starttls']) {
+ if (!@ldap_start_tls($this->con)){
+ msg('Starting TLS failed',-1);
+ if($this->cnf['debug'])
+ msg('LDAP TLS set: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ }
+ }
+ // needs version 3
+ if(isset($this->cnf['referrals'])) {
+ if(!@ldap_set_option($this->con, LDAP_OPT_REFERRALS,
+ $this->cnf['referrals'])){
+ msg('Setting LDAP referrals to off failed',-1);
+ if($this->cnf['debug'])
+ msg('LDAP referal set: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ }
+ }
+ }
+ }
+
+ //set deref mode
+ if($this->cnf['deref']){
+ if(!@ldap_set_option($this->con, LDAP_OPT_DEREF, $this->cnf['deref'])){
+ msg('Setting LDAP Deref mode '.$this->cnf['deref'].' failed',-1);
+ if($this->cnf['debug'])
+ msg('LDAP deref set: '.htmlspecialchars(ldap_error($this->con)),0,__LINE__,__FILE__);
+ }
+ }
+ /* As of PHP 5.3.0 we can set timeout to speedup skipping of invalid servers */
+ if (defined('LDAP_OPT_NETWORK_TIMEOUT')) {
+ ldap_set_option($this->con, LDAP_OPT_NETWORK_TIMEOUT, 1);
+ }
+ $bound = @ldap_bind($this->con);
+ if ($bound) {
+ break;
+ }
+ }
+
+ if(!$bound) {
+ msg("LDAP: couldn't connect to LDAP server",-1);
+ return false;
+ }
+
+
+ $this->canDo['getUsers'] = true;
+ return true;
+ }
+
+ /**
+ * Wraps around ldap_search, ldap_list or ldap_read depending on $scope
+ *
+ * @param $scope string - can be 'base', 'one' or 'sub'
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _ldapsearch($link_identifier, $base_dn, $filter, $scope='sub', $attributes=null,
+ $attrsonly=0, $sizelimit=0, $timelimit=0, $deref=LDAP_DEREF_NEVER){
+ if(is_null($attributes)) $attributes = array();
+
+ if($scope == 'base'){
+ return @ldap_read($link_identifier, $base_dn, $filter, $attributes,
+ $attrsonly, $sizelimit, $timelimit, $deref);
+ }elseif($scope == 'one'){
+ return @ldap_list($link_identifier, $base_dn, $filter, $attributes,
+ $attrsonly, $sizelimit, $timelimit, $deref);
+ }else{
+ return @ldap_search($link_identifier, $base_dn, $filter, $attributes,
+ $attrsonly, $sizelimit, $timelimit, $deref);
+ }
+ }
+}
diff --git a/lib/plugins/authldap/plugin.info.txt b/lib/plugins/authldap/plugin.info.txt
new file mode 100644
index 000000000..c36385224
--- /dev/null
+++ b/lib/plugins/authldap/plugin.info.txt
@@ -0,0 +1,7 @@
+base authldap
+author
+email
+date
+name ldap auth plugin
+desc Provides authentication against am LDAP server
+url
diff --git a/lib/plugins/authmysql/auth.php b/lib/plugins/authmysql/auth.php
new file mode 100644
index 000000000..8a8f9a488
--- /dev/null
+++ b/lib/plugins/authmysql/auth.php
@@ -0,0 +1,947 @@
+<?php
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * MySQLP authentication backend
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthias.grimmm@sourceforge.net>
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+class auth_plugin_authmysql extends DokuWiki_Auth_Plugin {
+ var $dbcon = 0;
+ var $dbver = 0; // database version
+ var $dbrev = 0; // database revision
+ var $dbsub = 0; // database subrevision
+ var $cnf = null;
+ var $defaultgroup = "";
+
+ /**
+ * Constructor
+ *
+ * checks if the mysql interface is available, otherwise it will
+ * set the variable $success of the basis class to false
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function __construct() {
+ global $conf;
+ $this->cnf = $conf['auth']['mysql'];
+
+ if (method_exists($this, 'auth_basic')){
+ parent::__construct();
+ }
+
+ if(!function_exists('mysql_connect')) {
+ if ($this->cnf['debug']){
+ msg("MySQL err: PHP MySQL extension not found.",-1,__LINE__,__FILE__);
+ }
+ $this->success = false;
+ return;
+ }
+
+ // default to UTF-8, you rarely want something else
+ if(!isset($this->cnf['charset'])) $this->cnf['charset'] = 'utf8';
+
+ $this->defaultgroup = $conf['defaultgroup'];
+
+ // set capabilities based upon config strings set
+ if (empty($this->cnf['server']) || empty($this->cnf['user']) ||
+ !isset($this->cnf['password']) || empty($this->cnf['database'])){
+
+ if ($this->cnf['debug']){
+ msg("MySQL err: insufficient configuration.",-1,__LINE__,__FILE__);
+ }
+ $this->success = false;
+ return;
+ }
+
+ $this->cando['addUser'] = $this->_chkcnf(array(
+ 'getUserInfo',
+ 'getGroups',
+ 'addUser',
+ 'getUserID',
+ 'getGroupID',
+ 'addGroup',
+ 'addUserGroup'),true);
+ $this->cando['delUser'] = $this->_chkcnf(array(
+ 'getUserID',
+ 'delUser',
+ 'delUserRefs'),true);
+ $this->cando['modLogin'] = $this->_chkcnf(array(
+ 'getUserID',
+ 'updateUser',
+ 'UpdateTarget'),true);
+ $this->cando['modPass'] = $this->cando['modLogin'];
+ $this->cando['modName'] = $this->cando['modLogin'];
+ $this->cando['modMail'] = $this->cando['modLogin'];
+ $this->cando['modGroups'] = $this->_chkcnf(array(
+ 'getUserID',
+ 'getGroups',
+ 'getGroupID',
+ 'addGroup',
+ 'addUserGroup',
+ 'delGroup',
+ 'getGroupID',
+ 'delUserGroup'),true);
+ /* getGroups is not yet supported
+ $this->cando['getGroups'] = $this->_chkcnf(array('getGroups',
+ 'getGroupID'),false); */
+ $this->cando['getUsers'] = $this->_chkcnf(array(
+ 'getUsers',
+ 'getUserInfo',
+ 'getGroups'),false);
+ $this->cando['getUserCount'] = $this->_chkcnf(array('getUsers'),false);
+ }
+
+ /**
+ * Check if the given config strings are set
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ * @return bool
+ */
+ function _chkcnf($keys, $wop=false){
+ foreach ($keys as $key){
+ if (empty($this->cnf[$key])) return false;
+ }
+
+ /* write operation and lock array filled with tables names? */
+ if ($wop && (!is_array($this->cnf['TablesToLock']) ||
+ !count($this->cnf['TablesToLock']))){
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the given user exists and the given plaintext password
+ * is correct. Furtheron it might be checked wether the user is
+ * member of the right group
+ *
+ * Depending on which SQL string is defined in the config, password
+ * checking is done here (getpass) or by the database (passcheck)
+ *
+ * @param $user user who would like access
+ * @param $pass user's clear text password to check
+ * @return bool
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function checkPass($user,$pass){
+ $rc = false;
+
+ if($this->_openDB()) {
+ $sql = str_replace('%{user}',$this->_escape($user),$this->cnf['checkPass']);
+ $sql = str_replace('%{pass}',$this->_escape($pass),$sql);
+ $sql = str_replace('%{dgroup}',$this->_escape($this->defaultgroup),$sql);
+ $result = $this->_queryDB($sql);
+
+ if($result !== false && count($result) == 1) {
+ if($this->cnf['forwardClearPass'] == 1)
+ $rc = true;
+ else
+ $rc = auth_verifyPassword($pass,$result[0]['pass']);
+ }
+ $this->_closeDB();
+ }
+ return $rc;
+ }
+
+ /**
+ * [public function]
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * @param $user user's nick to get data for
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function getUserData($user){
+ if($this->_openDB()) {
+ $this->_lockTables("READ");
+ $info = $this->_getUserInfo($user);
+ $this->_unlockTables();
+ $this->_closeDB();
+ } else
+ $info = false;
+ return $info;
+ }
+
+ /**
+ * [public function]
+ *
+ * Create a new User. Returns false if the user already exists,
+ * null when an error occurred and true if everything went well.
+ *
+ * The new user will be added to the default group by this
+ * function if grps are not specified (default behaviour).
+ *
+ * @param $user nick of the user
+ * @param $pwd clear text password
+ * @param $name full name of the user
+ * @param $mail email address
+ * @param $grps array of groups the user should become member of
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function createUser($user,$pwd,$name,$mail,$grps=null){
+ if($this->_openDB()) {
+ if (($info = $this->_getUserInfo($user)) !== false)
+ return false; // user already exists
+
+ // set defaultgroup if no groups were given
+ if ($grps == null)
+ $grps = array($this->defaultgroup);
+
+ $this->_lockTables("WRITE");
+ $pwd = $this->cnf['forwardClearPass'] ? $pwd : auth_cryptPassword($pwd);
+ $rc = $this->_addUser($user,$pwd,$name,$mail,$grps);
+ $this->_unlockTables();
+ $this->_closeDB();
+ if ($rc) return true;
+ }
+ return null; // return error
+ }
+
+ /**
+ * Modify user data [public function]
+ *
+ * An existing user dataset will be modified. Changes are given in an array.
+ *
+ * The dataset update will be rejected if the user name should be changed
+ * to an already existing one.
+ *
+ * The password must be provides unencrypted. Pasword cryption is done
+ * automatically if configured.
+ *
+ * If one or more groups could't be updated, an error would be set. In
+ * this case the dataset might already be changed and we can't rollback
+ * the changes. Transactions would be really usefull here.
+ *
+ * modifyUser() may be called without SQL statements defined that are
+ * needed to change group membership (for example if only the user profile
+ * should be modified). In this case we asure that we don't touch groups
+ * even $changes['grps'] is set by mistake.
+ *
+ * @param $user nick of the user to be changed
+ * @param $changes array of field/value pairs to be changed (password
+ * will be clear text)
+ * @return bool true on success, false on error
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function modifyUser($user, $changes) {
+ $rc = false;
+
+ if (!is_array($changes) || !count($changes))
+ return true; // nothing to change
+
+ if($this->_openDB()) {
+ $this->_lockTables("WRITE");
+
+ if (($uid = $this->_getUserID($user))) {
+ $rc = $this->_updateUserInfo($changes, $uid);
+
+ if ($rc && isset($changes['grps']) && $this->cando['modGroups']) {
+ $groups = $this->_getGroups($user);
+ $grpadd = array_diff($changes['grps'], $groups);
+ $grpdel = array_diff($groups, $changes['grps']);
+
+ foreach($grpadd as $group)
+ if (($this->_addUserToGroup($user, $group, 1)) == false)
+ $rc = false;
+
+ foreach($grpdel as $group)
+ if (($this->_delUserFromGroup($user, $group)) == false)
+ $rc = false;
+ }
+ }
+
+ $this->_unlockTables();
+ $this->_closeDB();
+ }
+ return $rc;
+ }
+
+ /**
+ * [public function]
+ *
+ * Remove one or more users from the list of registered users
+ *
+ * @param array $users array of users to be deleted
+ * @return int the number of users deleted
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function deleteUsers($users) {
+ $count = 0;
+
+ if($this->_openDB()) {
+ if (is_array($users) && count($users)) {
+ $this->_lockTables("WRITE");
+ foreach ($users as $user) {
+ if ($this->_delUser($user))
+ $count++;
+ }
+ $this->_unlockTables();
+ }
+ $this->_closeDB();
+ }
+ return $count;
+ }
+
+ /**
+ * [public function]
+ *
+ * Counts users which meet certain $filter criteria.
+ *
+ * @param array $filter filter criteria in item/pattern pairs
+ * @return count of found users.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function getUserCount($filter=array()) {
+ $rc = 0;
+
+ if($this->_openDB()) {
+ $sql = $this->_createSQLFilter($this->cnf['getUsers'], $filter);
+
+ if ($this->dbver >= 4) {
+ $sql = substr($sql, 6); /* remove 'SELECT' or 'select' */
+ $sql = "SELECT SQL_CALC_FOUND_ROWS".$sql." LIMIT 1";
+ $this->_queryDB($sql);
+ $result = $this->_queryDB("SELECT FOUND_ROWS()");
+ $rc = $result[0]['FOUND_ROWS()'];
+ } else if (($result = $this->_queryDB($sql)))
+ $rc = count($result);
+
+ $this->_closeDB();
+ }
+ return $rc;
+ }
+
+ /**
+ * Bulk retrieval of user data. [public function]
+ *
+ * @param first index of first user to be returned
+ * @param limit max number of users to be returned
+ * @param filter array of field/pattern pairs
+ * @return array of userinfo (refer getUserData for internal userinfo details)
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function retrieveUsers($first=0,$limit=10,$filter=array()) {
+ $out = array();
+
+ if($this->_openDB()) {
+ $this->_lockTables("READ");
+ $sql = $this->_createSQLFilter($this->cnf['getUsers'], $filter);
+ $sql .= " ".$this->cnf['SortOrder']." LIMIT $first, $limit";
+ $result = $this->_queryDB($sql);
+
+ if (!empty($result)) {
+ foreach ($result as $user)
+ if (($info = $this->_getUserInfo($user['user'])))
+ $out[$user['user']] = $info;
+ }
+
+ $this->_unlockTables();
+ $this->_closeDB();
+ }
+ return $out;
+ }
+
+ /**
+ * Give user membership of a group [public function]
+ *
+ * @param $user
+ * @param $group
+ * @return bool true on success, false on error
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function joinGroup($user, $group) {
+ $rc = false;
+
+ if ($this->_openDB()) {
+ $this->_lockTables("WRITE");
+ $rc = $this->_addUserToGroup($user, $group);
+ $this->_unlockTables();
+ $this->_closeDB();
+ }
+ return $rc;
+ }
+
+ /**
+ * Remove user from a group [public function]
+ *
+ * @param $user user that leaves a group
+ * @param $group group to leave
+ * @return bool
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function leaveGroup($user, $group) {
+ $rc = false;
+
+ if ($this->_openDB()) {
+ $this->_lockTables("WRITE");
+ $uid = $this->_getUserID($user);
+ $rc = $this->_delUserFromGroup($user, $group);
+ $this->_unlockTables();
+ $this->_closeDB();
+ }
+ return $rc;
+ }
+
+ /**
+ * MySQL is case-insensitive
+ */
+ function isCaseSensitive(){
+ return false;
+ }
+
+ /**
+ * Adds a user to a group.
+ *
+ * If $force is set to '1' non existing groups would be created.
+ *
+ * The database connection must already be established. Otherwise
+ * this function does nothing and returns 'false'. It is strongly
+ * recommended to call this function only after all participating
+ * tables (group and usergroup) have been locked.
+ *
+ * @param $user user to add to a group
+ * @param $group name of the group
+ * @param $force '1' create missing groups
+ * @return bool 'true' on success, 'false' on error
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _addUserToGroup($user, $group, $force=0) {
+ $newgroup = 0;
+
+ if (($this->dbcon) && ($user)) {
+ $gid = $this->_getGroupID($group);
+ if (!$gid) {
+ if ($force) { // create missing groups
+ $sql = str_replace('%{group}',$this->_escape($group),$this->cnf['addGroup']);
+ $gid = $this->_modifyDB($sql);
+ $newgroup = 1; // group newly created
+ }
+ if (!$gid) return false; // group didn't exist and can't be created
+ }
+
+ $sql = $this->cnf['addUserGroup'];
+ if(strpos($sql,'%{uid}') !== false){
+ $uid = $this->_getUserID($user);
+ $sql = str_replace('%{uid}', $this->_escape($uid),$sql);
+ }
+ $sql = str_replace('%{user}', $this->_escape($user),$sql);
+ $sql = str_replace('%{gid}', $this->_escape($gid),$sql);
+ $sql = str_replace('%{group}',$this->_escape($group),$sql);
+ if ($this->_modifyDB($sql) !== false) return true;
+
+ if ($newgroup) { // remove previously created group on error
+ $sql = str_replace('%{gid}', $this->_escape($gid),$this->cnf['delGroup']);
+ $sql = str_replace('%{group}',$this->_escape($group),$sql);
+ $this->_modifyDB($sql);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove user from a group
+ *
+ * @param $user user that leaves a group
+ * @param $group group to leave
+ * @return bool true on success, false on error
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _delUserFromGroup($user, $group) {
+ $rc = false;
+
+ if (($this->dbcon) && ($user)) {
+ $sql = $this->cnf['delUserGroup'];
+ if(strpos($sql,'%{uid}') !== false){
+ $uid = $this->_getUserID($user);
+ $sql = str_replace('%{uid}', $this->_escape($uid),$sql);
+ }
+ $gid = $this->_getGroupID($group);
+ if ($gid) {
+ $sql = str_replace('%{user}', $this->_escape($user),$sql);
+ $sql = str_replace('%{gid}', $this->_escape($gid),$sql);
+ $sql = str_replace('%{group}',$this->_escape($group),$sql);
+ $rc = $this->_modifyDB($sql) == 0 ? true : false;
+ }
+ }
+ return $rc;
+ }
+
+ /**
+ * Retrieves a list of groups the user is a member off.
+ *
+ * The database connection must already be established
+ * for this function to work. Otherwise it will return
+ * 'false'.
+ *
+ * @param $user user whose groups should be listed
+ * @return bool false on error
+ * @return array array containing all groups on success
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _getGroups($user) {
+ $groups = array();
+
+ if($this->dbcon) {
+ $sql = str_replace('%{user}',$this->_escape($user),$this->cnf['getGroups']);
+ $result = $this->_queryDB($sql);
+
+ if($result !== false && count($result)) {
+ foreach($result as $row)
+ $groups[] = $row['group'];
+ }
+ return $groups;
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves the user id of a given user name
+ *
+ * The database connection must already be established
+ * for this function to work. Otherwise it will return
+ * 'false'.
+ *
+ * @param $user user whose id is desired
+ * @return user id
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _getUserID($user) {
+ if($this->dbcon) {
+ $sql = str_replace('%{user}',$this->_escape($user),$this->cnf['getUserID']);
+ $result = $this->_queryDB($sql);
+ return $result === false ? false : $result[0]['id'];
+ }
+ return false;
+ }
+
+ /**
+ * Adds a new User to the database.
+ *
+ * The database connection must already be established
+ * for this function to work. Otherwise it will return
+ * 'false'.
+ *
+ * @param $user login of the user
+ * @param $pwd encrypted password
+ * @param $name full name of the user
+ * @param $mail email address
+ * @param $grps array of groups the user should become member of
+ * @return bool
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _addUser($user,$pwd,$name,$mail,$grps){
+ if($this->dbcon && is_array($grps)) {
+ $sql = str_replace('%{user}', $this->_escape($user),$this->cnf['addUser']);
+ $sql = str_replace('%{pass}', $this->_escape($pwd),$sql);
+ $sql = str_replace('%{name}', $this->_escape($name),$sql);
+ $sql = str_replace('%{email}',$this->_escape($mail),$sql);
+ $uid = $this->_modifyDB($sql);
+
+ if ($uid) {
+ foreach($grps as $group) {
+ $gid = $this->_addUserToGroup($user, $group, 1);
+ if ($gid === false) break;
+ }
+
+ if ($gid) return true;
+ else {
+ /* remove the new user and all group relations if a group can't
+ * be assigned. Newly created groups will remain in the database
+ * and won't be removed. This might create orphaned groups but
+ * is not a big issue so we ignore this problem here.
+ */
+ $this->_delUser($user);
+ if ($this->cnf['debug'])
+ msg ("MySQL err: Adding user '$user' to group '$group' failed.",-1,__LINE__,__FILE__);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Deletes a given user and all his group references.
+ *
+ * The database connection must already be established
+ * for this function to work. Otherwise it will return
+ * 'false'.
+ *
+ * @param $user user whose id is desired
+ * @return bool
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _delUser($user) {
+ if($this->dbcon) {
+ $uid = $this->_getUserID($user);
+ if ($uid) {
+ $sql = str_replace('%{uid}',$this->_escape($uid),$this->cnf['delUserRefs']);
+ $this->_modifyDB($sql);
+ $sql = str_replace('%{uid}',$this->_escape($uid),$this->cnf['delUser']);
+ $sql = str_replace('%{user}', $this->_escape($user),$sql);
+ $this->_modifyDB($sql);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * getUserInfo
+ *
+ * Gets the data for a specific user The database connection
+ * must already be established for this function to work.
+ * Otherwise it will return 'false'.
+ *
+ * @param $user user's nick to get data for
+ * @return bool false on error
+ * @return array user info on success
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _getUserInfo($user){
+ $sql = str_replace('%{user}',$this->_escape($user),$this->cnf['getUserInfo']);
+ $result = $this->_queryDB($sql);
+ if($result !== false && count($result)) {
+ $info = $result[0];
+ $info['grps'] = $this->_getGroups($user);
+ return $info;
+ }
+ return false;
+ }
+
+ /**
+ * Updates the user info in the database
+ *
+ * Update a user data structure in the database according changes
+ * given in an array. The user name can only be changes if it didn't
+ * exists already. If the new user name exists the update procedure
+ * will be aborted. The database keeps unchanged.
+ *
+ * The database connection has already to be established for this
+ * function to work. Otherwise it will return 'false'.
+ *
+ * The password will be crypted if necessary.
+ *
+ * @param $changes array of items to change as pairs of item and value
+ * @param $uid user id of dataset to change, must be unique in DB
+ * @return true on success or false on error
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _updateUserInfo($changes, $uid) {
+ $sql = $this->cnf['updateUser']." ";
+ $cnt = 0;
+ $err = 0;
+
+ if($this->dbcon) {
+ foreach ($changes as $item => $value) {
+ if ($item == 'user') {
+ if (($this->_getUserID($changes['user']))) {
+ $err = 1; /* new username already exists */
+ break; /* abort update */
+ }
+ if ($cnt++ > 0) $sql .= ", ";
+ $sql .= str_replace('%{user}',$value,$this->cnf['UpdateLogin']);
+ } else if ($item == 'name') {
+ if ($cnt++ > 0) $sql .= ", ";
+ $sql .= str_replace('%{name}',$value,$this->cnf['UpdateName']);
+ } else if ($item == 'pass') {
+ if (!$this->cnf['forwardClearPass'])
+ $value = auth_cryptPassword($value);
+ if ($cnt++ > 0) $sql .= ", ";
+ $sql .= str_replace('%{pass}',$value,$this->cnf['UpdatePass']);
+ } else if ($item == 'mail') {
+ if ($cnt++ > 0) $sql .= ", ";
+ $sql .= str_replace('%{email}',$value,$this->cnf['UpdateEmail']);
+ }
+ }
+
+ if ($err == 0) {
+ if ($cnt > 0) {
+ $sql .= " ".str_replace('%{uid}', $uid, $this->cnf['UpdateTarget']);
+ if(get_class($this) == 'auth_mysql') $sql .= " LIMIT 1"; //some PgSQL inheritance comp.
+ $this->_modifyDB($sql);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves the group id of a given group name
+ *
+ * The database connection must already be established
+ * for this function to work. Otherwise it will return
+ * 'false'.
+ *
+ * @param $group group name which id is desired
+ * @return group id
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _getGroupID($group) {
+ if($this->dbcon) {
+ $sql = str_replace('%{group}',$this->_escape($group),$this->cnf['getGroupID']);
+ $result = $this->_queryDB($sql);
+ return $result === false ? false : $result[0]['id'];
+ }
+ return false;
+ }
+
+ /**
+ * Opens a connection to a database and saves the handle for further
+ * usage in the object. The successful call to this functions is
+ * essential for most functions in this object.
+ *
+ * @return bool
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _openDB() {
+ if (!$this->dbcon) {
+ $con = @mysql_connect ($this->cnf['server'], $this->cnf['user'], $this->cnf['password']);
+ if ($con) {
+ if ((mysql_select_db($this->cnf['database'], $con))) {
+ if ((preg_match("/^(\d+)\.(\d+)\.(\d+).*/", mysql_get_server_info ($con), $result)) == 1) {
+ $this->dbver = $result[1];
+ $this->dbrev = $result[2];
+ $this->dbsub = $result[3];
+ }
+ $this->dbcon = $con;
+ if(!empty($this->cnf['charset'])){
+ mysql_query('SET CHARACTER SET "' . $this->cnf['charset'] . '"', $con);
+ }
+ return true; // connection and database successfully opened
+ } else {
+ mysql_close ($con);
+ if ($this->cnf['debug'])
+ msg("MySQL err: No access to database {$this->cnf['database']}.",-1,__LINE__,__FILE__);
+ }
+ } else if ($this->cnf['debug'])
+ msg ("MySQL err: Connection to {$this->cnf['user']}@{$this->cnf['server']} not possible.",
+ -1,__LINE__,__FILE__);
+
+ return false; // connection failed
+ }
+ return true; // connection already open
+ }
+
+ /**
+ * Closes a database connection.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _closeDB() {
+ if ($this->dbcon) {
+ mysql_close ($this->dbcon);
+ $this->dbcon = 0;
+ }
+ }
+
+ /**
+ * Sends a SQL query to the database and transforms the result into
+ * an associative array.
+ *
+ * This function is only able to handle queries that returns a
+ * table such as SELECT.
+ *
+ * @param $query SQL string that contains the query
+ * @return array with the result table
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _queryDB($query) {
+ if($this->cnf['debug'] >= 2){
+ msg('MySQL query: '.hsc($query),0,__LINE__,__FILE__);
+ }
+
+ $resultarray = array();
+ if ($this->dbcon) {
+ $result = @mysql_query($query,$this->dbcon);
+ if ($result) {
+ while (($t = mysql_fetch_assoc($result)) !== false)
+ $resultarray[]=$t;
+ mysql_free_result ($result);
+ return $resultarray;
+ }
+ if ($this->cnf['debug'])
+ msg('MySQL err: '.mysql_error($this->dbcon),-1,__LINE__,__FILE__);
+ }
+ return false;
+ }
+
+ /**
+ * Sends a SQL query to the database
+ *
+ * This function is only able to handle queries that returns
+ * either nothing or an id value such as INPUT, DELETE, UPDATE, etc.
+ *
+ * @param $query SQL string that contains the query
+ * @return insert id or 0, false on error
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _modifyDB($query) {
+ if ($this->dbcon) {
+ $result = @mysql_query($query,$this->dbcon);
+ if ($result) {
+ $rc = mysql_insert_id($this->dbcon); //give back ID on insert
+ if ($rc !== false) return $rc;
+ }
+ if ($this->cnf['debug'])
+ msg('MySQL err: '.mysql_error($this->dbcon),-1,__LINE__,__FILE__);
+ }
+ return false;
+ }
+
+ /**
+ * Locked a list of tables for exclusive access so that modifications
+ * to the database can't be disturbed by other threads. The list
+ * could be set with $conf['auth']['mysql']['TablesToLock'] = array()
+ *
+ * If aliases for tables are used in SQL statements, also this aliases
+ * must be locked. For eg. you use a table 'user' and the alias 'u' in
+ * some sql queries, the array must looks like this (order is important):
+ * array("user", "user AS u");
+ *
+ * MySQL V3 is not able to handle transactions with COMMIT/ROLLBACK
+ * so that this functionality is simulated by this function. Nevertheless
+ * it is not as powerful as transactions, it is a good compromise in safty.
+ *
+ * @param $mode could be 'READ' or 'WRITE'
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _lockTables($mode) {
+ if ($this->dbcon) {
+ if (is_array($this->cnf['TablesToLock']) && !empty($this->cnf['TablesToLock'])) {
+ if ($mode == "READ" || $mode == "WRITE") {
+ $sql = "LOCK TABLES ";
+ $cnt = 0;
+ foreach ($this->cnf['TablesToLock'] as $table) {
+ if ($cnt++ != 0) $sql .= ", ";
+ $sql .= "$table $mode";
+ }
+ $this->_modifyDB($sql);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Unlock locked tables. All existing locks of this thread will be
+ * abrogated.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _unlockTables() {
+ if ($this->dbcon) {
+ $this->_modifyDB("UNLOCK TABLES");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Transforms the filter settings in an filter string for a SQL database
+ * The database connection must already be established, otherwise the
+ * original SQL string without filter criteria will be returned.
+ *
+ * @param $sql SQL string to which the $filter criteria should be added
+ * @param $filter array of filter criteria as pairs of item and pattern
+ * @return SQL string with attached $filter criteria on success
+ * @return the original SQL string on error.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+ function _createSQLFilter($sql, $filter) {
+ $SQLfilter = "";
+ $cnt = 0;
+
+ if ($this->dbcon) {
+ foreach ($filter as $item => $pattern) {
+ $tmp = '%'.$this->_escape($pattern).'%';
+ if ($item == 'user') {
+ if ($cnt++ > 0) $SQLfilter .= " AND ";
+ $SQLfilter .= str_replace('%{user}',$tmp,$this->cnf['FilterLogin']);
+ } else if ($item == 'name') {
+ if ($cnt++ > 0) $SQLfilter .= " AND ";
+ $SQLfilter .= str_replace('%{name}',$tmp,$this->cnf['FilterName']);
+ } else if ($item == 'mail') {
+ if ($cnt++ > 0) $SQLfilter .= " AND ";
+ $SQLfilter .= str_replace('%{email}',$tmp,$this->cnf['FilterEmail']);
+ } else if ($item == 'grps') {
+ if ($cnt++ > 0) $SQLfilter .= " AND ";
+ $SQLfilter .= str_replace('%{group}',$tmp,$this->cnf['FilterGroup']);
+ }
+ }
+
+ // we have to check SQLfilter here and must not use $cnt because if
+ // any of cnf['Filter????'] is not defined, a malformed SQL string
+ // would be generated.
+
+ if (strlen($SQLfilter)) {
+ $glue = strpos(strtolower($sql),"where") ? " AND " : " WHERE ";
+ $sql = $sql.$glue.$SQLfilter;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Escape a string for insertion into the database
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $string The string to escape
+ * @param boolean $like Escape wildcard chars as well?
+ */
+ function _escape($string,$like=false){
+ if($this->dbcon){
+ $string = mysql_real_escape_string($string, $this->dbcon);
+ }else{
+ $string = addslashes($string);
+ }
+ if($like){
+ $string = addcslashes($string,'%_');
+ }
+ return $string;
+ }
+}
diff --git a/lib/plugins/authmysql/plugin.info.txt b/lib/plugins/authmysql/plugin.info.txt
new file mode 100644
index 000000000..d08d4a7ef
--- /dev/null
+++ b/lib/plugins/authmysql/plugin.info.txt
@@ -0,0 +1,7 @@
+base authmysql
+author
+email
+date
+name mysql auth plugin
+desc Provides authentication against a MySQL Server
+url
diff --git a/lib/plugins/authpgsql/auth.php b/lib/plugins/authpgsql/auth.php
new file mode 100644
index 000000000..824a77882
--- /dev/null
+++ b/lib/plugins/authpgsql/auth.php
@@ -0,0 +1,331 @@
+<?php
+/**
+ * Plugin auth provider
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * PgSQL authentication backend
+ *
+ * This class inherits much functionality from the MySQL class
+ * and just reimplements the Postgres specific parts.
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthias.grimmm@sourceforge.net>
+ * @author Jan Schumann <js@schumann-it.com>
+*/
+class auth_plugin_authpgsql extends auth_plugin_authmysql
+{
+ var $cnf = null;
+ var $opts = null;
+ var $adldap = null;
+ var $users = null;
+
+ /**
+ * Constructor
+ */
+ function auth_plugin_authpgsql() {
+ global $conf;
+ $this->cnf = $conf['auth']['ad'];
+
+
+ // additional information fields
+ if (isset($this->cnf['additional'])) {
+ $this->cnf['additional'] = str_replace(' ', '', $this->cnf['additional']);
+ $this->cnf['additional'] = explode(',', $this->cnf['additional']);
+ } else $this->cnf['additional'] = array();
+
+ // ldap extension is needed
+ if (!function_exists('ldap_connect')) {
+ if ($this->cnf['debug'])
+ msg("AD Auth: PHP LDAP extension not found.",-1);
+ $this->success = false;
+ return;
+ }
+
+ // Prepare SSO
+ if($_SERVER['REMOTE_USER'] && $this->cnf['sso']){
+ // remove possible NTLM domain
+ list($dom,$usr) = explode('\\',$_SERVER['REMOTE_USER'],2);
+ if(!$usr) $usr = $dom;
+
+ // remove possible Kerberos domain
+ list($usr,$dom) = explode('@',$usr);
+
+ $dom = strtolower($dom);
+ $_SERVER['REMOTE_USER'] = $usr;
+
+ // we need to simulate a login
+ if(empty($_COOKIE[DOKU_COOKIE])){
+ $_REQUEST['u'] = $_SERVER['REMOTE_USER'];
+ $_REQUEST['p'] = 'sso_only';
+ }
+ }
+
+ // prepare adLDAP standard configuration
+ $this->opts = $this->cnf;
+
+ // add possible domain specific configuration
+ if($dom && is_array($this->cnf[$dom])) foreach($this->cnf[$dom] as $key => $val){
+ $this->opts[$key] = $val;
+ }
+
+ // handle multiple AD servers
+ $this->opts['domain_controllers'] = explode(',',$this->opts['domain_controllers']);
+ $this->opts['domain_controllers'] = array_map('trim',$this->opts['domain_controllers']);
+ $this->opts['domain_controllers'] = array_filter($this->opts['domain_controllers']);
+
+ // we can change the password if SSL is set
+ if($this->opts['use_ssl'] || $this->opts['use_tls']){
+ $this->cando['modPass'] = true;
+ }
+ $this->cando['modName'] = true;
+ $this->cando['modMail'] = true;
+ }
+
+ /**
+ * Check user+password [required auth function]
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct by trying to bind
+ * to the LDAP server
+ *
+ * @author James Van Lommel <james@nosq.com>
+ * @return bool
+ */
+ function checkPass($user, $pass){
+ if($_SERVER['REMOTE_USER'] &&
+ $_SERVER['REMOTE_USER'] == $user &&
+ $this->cnf['sso']) return true;
+
+ if(!$this->_init()) return false;
+ return $this->adldap->authenticate($user, $pass);
+ }
+
+ /**
+ * Return user info [required auth function]
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email address of the user
+ * grps array list of groups the user is in
+ *
+ * This LDAP specific function returns the following
+ * addional fields:
+ *
+ * dn string distinguished name (DN)
+ * uid string Posix User ID
+ *
+ * @author James Van Lommel <james@nosq.com>
+ */
+ function getUserData($user){
+ global $conf;
+ if(!$this->_init()) return false;
+
+ $fields = array('mail','displayname','samaccountname');
+
+ // add additional fields to read
+ $fields = array_merge($fields, $this->cnf['additional']);
+ $fields = array_unique($fields);
+
+ //get info for given user
+ $result = $this->adldap->user_info($user, $fields);
+ //general user info
+ $info['name'] = $result[0]['displayname'][0];
+ $info['mail'] = $result[0]['mail'][0];
+ $info['uid'] = $result[0]['samaccountname'][0];
+ $info['dn'] = $result[0]['dn'];
+
+ // additional information
+ foreach ($this->cnf['additional'] as $field) {
+ if (isset($result[0][strtolower($field)])) {
+ $info[$field] = $result[0][strtolower($field)][0];
+ }
+ }
+
+ // handle ActiveDirectory memberOf
+ $info['grps'] = $this->adldap->user_groups($user,(bool) $this->opts['recursive_groups']);
+
+ if (is_array($info['grps'])) {
+ foreach ($info['grps'] as $ndx => $group) {
+ $info['grps'][$ndx] = $this->cleanGroup($group);
+ }
+ }
+
+ // always add the default group to the list of groups
+ if(!is_array($info['grps']) || !in_array($conf['defaultgroup'],$info['grps'])){
+ $info['grps'][] = $conf['defaultgroup'];
+ }
+
+ return $info;
+ }
+
+ /**
+ * Make AD group names usable by DokuWiki.
+ *
+ * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
+ *
+ * @author James Van Lommel (jamesvl@gmail.com)
+ */
+ function cleanGroup($name) {
+ $sName = str_replace('\\', '', $name);
+ $sName = str_replace('#', '', $sName);
+ $sName = preg_replace('[\s]', '_', $sName);
+ return $sName;
+ }
+
+ /**
+ * Sanitize user names
+ */
+ function cleanUser($name) {
+ return $this->cleanGroup($name);
+ }
+
+ /**
+ * Most values in LDAP are case-insensitive
+ */
+ function isCaseSensitive(){
+ return false;
+ }
+
+ /**
+ * Bulk retrieval of user data
+ *
+ * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
+ * @param start index of first user to be returned
+ * @param limit max number of users to be returned
+ * @param filter array of field/pattern pairs, null for no filter
+ * @return array of userinfo (refer getUserData for internal userinfo details)
+ */
+ function retrieveUsers($start=0,$limit=-1,$filter=array()) {
+ if(!$this->_init()) return false;
+
+ if ($this->users === null) {
+ //get info for given user
+ $result = $this->adldap->all_users();
+ if (!$result) return array();
+ $this->users = array_fill_keys($result, false);
+ }
+
+ $i = 0;
+ $count = 0;
+ $this->_constructPattern($filter);
+ $result = array();
+
+ foreach ($this->users as $user => &$info) {
+ if ($i++ < $start) {
+ continue;
+ }
+ if ($info === false) {
+ $info = $this->getUserData($user);
+ }
+ if ($this->_filter($user, $info)) {
+ $result[$user] = $info;
+ if (($limit >= 0) && (++$count >= $limit)) break;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Modify user data
+ *
+ * @param $user nick of the user to be changed
+ * @param $changes array of field/value pairs to be changed
+ * @return bool
+ */
+ function modifyUser($user, $changes) {
+ $return = true;
+
+ // password changing
+ if(isset($changes['pass'])){
+ try {
+ $return = $this->adldap->user_password($user,$changes['pass']);
+ } catch (adLDAPException $e) {
+ if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
+ $return = false;
+ }
+ if(!$return) msg('AD Auth: failed to change the password. Maybe the password policy was not met?',-1);
+ }
+
+ // changing user data
+ $adchanges = array();
+ if(isset($changes['name'])){
+ // get first and last name
+ $parts = explode(' ',$changes['name']);
+ $adchanges['surname'] = array_pop($parts);
+ $adchanges['firstname'] = join(' ',$parts);
+ $adchanges['display_name'] = $changes['name'];
+ }
+ if(isset($changes['mail'])){
+ $adchanges['email'] = $changes['mail'];
+ }
+ if(count($adchanges)){
+ try {
+ $return = $return & $this->adldap->user_modify($user,$adchanges);
+ } catch (adLDAPException $e) {
+ if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
+ $return = false;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Initialize the AdLDAP library and connect to the server
+ */
+ function _init(){
+ if(!is_null($this->adldap)) return true;
+
+ // connect
+ try {
+ $this->adldap = new adLDAP($this->opts);
+ if (isset($this->opts['ad_username']) && isset($this->opts['ad_password'])) {
+ $this->canDo['getUsers'] = true;
+ }
+ return true;
+ } catch (adLDAPException $e) {
+ if ($this->cnf['debug']) {
+ msg('AD Auth: '.$e->getMessage(), -1);
+ }
+ $this->success = false;
+ $this->adldap = null;
+ }
+ return false;
+ }
+
+ /**
+ * return 1 if $user + $info match $filter criteria, 0 otherwise
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+ function _filter($user, $info) {
+ foreach ($this->_pattern as $item => $pattern) {
+ if ($item == 'user') {
+ if (!preg_match($pattern, $user)) return 0;
+ } else if ($item == 'grps') {
+ if (!count(preg_grep($pattern, $info['grps']))) return 0;
+ } else {
+ if (!preg_match($pattern, $info[$item])) return 0;
+ }
+ }
+ return 1;
+ }
+
+ function _constructPattern($filter) {
+ $this->_pattern = array();
+ foreach ($filter as $item => $pattern) {
+// $this->_pattern[$item] = '/'.preg_quote($pattern,"/").'/i'; // don't allow regex characters
+ $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i'; // allow regex characters
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/plugins/authpgsql/plugin.info.txt b/lib/plugins/authpgsql/plugin.info.txt
new file mode 100644
index 000000000..ad565b853
--- /dev/null
+++ b/lib/plugins/authpgsql/plugin.info.txt
@@ -0,0 +1,7 @@
+base authad
+author
+email
+date
+name active directory auth plugin
+desc Provides authentication against a Microsoft Active Directory
+url
diff --git a/lib/plugins/authplain/auth.php b/lib/plugins/authplain/auth.php
new file mode 100644
index 000000000..570d029ff
--- /dev/null
+++ b/lib/plugins/authplain/auth.php
@@ -0,0 +1,328 @@
+<?php
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Plaintext authentication backend
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+class auth_plugin_authplain extends DokuWiki_Auth_Plugin {
+ var $users = null;
+ var $_pattern = array();
+
+ /**
+ * Constructor
+ *
+ * Carry out sanity checks to ensure the object is
+ * able to operate. Set capabilities.
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+ function __construct() {
+ global $config_cascade;
+
+ if (!@is_readable($config_cascade['plainauth.users']['default'])){
+ $this->success = false;
+ }else{
+ if(@is_writable($config_cascade['plainauth.users']['default'])){
+ $this->cando['addUser'] = true;
+ $this->cando['delUser'] = true;
+ $this->cando['modLogin'] = true;
+ $this->cando['modPass'] = true;
+ $this->cando['modName'] = true;
+ $this->cando['modMail'] = true;
+ $this->cando['modGroups'] = true;
+ }
+ $this->cando['getUsers'] = true;
+ $this->cando['getUserCount'] = true;
+ }
+ }
+
+ /**
+ * Check user+password [required auth function]
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool
+ */
+ function checkPass($user,$pass){
+
+ $userinfo = $this->getUserData($user);
+ if ($userinfo === false) return false;
+
+ return auth_verifyPassword($pass,$this->users[$user]['pass']);
+ }
+
+ /**
+ * Return user info
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function getUserData($user){
+
+ if($this->users === null) $this->_loadUserData();
+ return isset($this->users[$user]) ? $this->users[$user] : false;
+ }
+
+ /**
+ * Create a new User
+ *
+ * Returns false if the user already exists, null when an error
+ * occurred and true if everything went well.
+ *
+ * The new user will be added to the default group by this
+ * function if grps are not specified (default behaviour).
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+ function createUser($user,$pwd,$name,$mail,$grps=null){
+ global $conf;
+ global $config_cascade;
+
+ // user mustn't already exist
+ if ($this->getUserData($user) !== false) return false;
+
+ $pass = auth_cryptPassword($pwd);
+
+ // set default group if no groups specified
+ if (!is_array($grps)) $grps = array($conf['defaultgroup']);
+
+ // prepare user line
+ $groups = join(',',$grps);
+ $userline = join(':',array($user,$pass,$name,$mail,$groups))."\n";
+
+ if (io_saveFile($config_cascade['plainauth.users']['default'],$userline,true)) {
+ $this->users[$user] = compact('pass','name','mail','grps');
+ return $pwd;
+ }
+
+ msg('The '.$config_cascade['plainauth.users']['default'].
+ ' file is not writable. Please inform the Wiki-Admin',-1);
+ return null;
+ }
+
+ /**
+ * Modify user data
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param $user nick of the user to be changed
+ * @param $changes array of field/value pairs to be changed (password will be clear text)
+ * @return bool
+ */
+ function modifyUser($user, $changes) {
+ global $conf;
+ global $ACT;
+ global $INFO;
+ global $config_cascade;
+
+ // sanity checks, user must already exist and there must be something to change
+ if (($userinfo = $this->getUserData($user)) === false) return false;
+ if (!is_array($changes) || !count($changes)) return true;
+
+ // update userinfo with new data, remembering to encrypt any password
+ $newuser = $user;
+ foreach ($changes as $field => $value) {
+ if ($field == 'user') {
+ $newuser = $value;
+ continue;
+ }
+ if ($field == 'pass') $value = auth_cryptPassword($value);
+ $userinfo[$field] = $value;
+ }
+
+ $groups = join(',',$userinfo['grps']);
+ $userline = join(':',array($newuser, $userinfo['pass'], $userinfo['name'], $userinfo['mail'], $groups))."\n";
+
+ if (!$this->deleteUsers(array($user))) {
+ msg('Unable to modify user data. Please inform the Wiki-Admin',-1);
+ return false;
+ }
+
+ if (!io_saveFile($config_cascade['plainauth.users']['default'],$userline,true)) {
+ msg('There was an error modifying your user data. You should register again.',-1);
+ // FIXME, user has been deleted but not recreated, should force a logout and redirect to login page
+ $ACT == 'register';
+ return false;
+ }
+
+ $this->users[$newuser] = $userinfo;
+ return true;
+ }
+
+ /**
+ * Remove one or more users from the list of registered users
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @param array $users array of users to be deleted
+ * @return int the number of users deleted
+ */
+ function deleteUsers($users) {
+ global $config_cascade;
+
+ if (!is_array($users) || empty($users)) return 0;
+
+ if ($this->users === null) $this->_loadUserData();
+
+ $deleted = array();
+ foreach ($users as $user) {
+ if (isset($this->users[$user])) $deleted[] = preg_quote($user,'/');
+ }
+
+ if (empty($deleted)) return 0;
+
+ $pattern = '/^('.join('|',$deleted).'):/';
+
+ if (io_deleteFromFile($config_cascade['plainauth.users']['default'],$pattern,true)) {
+ foreach ($deleted as $user) unset($this->users[$user]);
+ return count($deleted);
+ }
+
+ // problem deleting, reload the user list and count the difference
+ $count = count($this->users);
+ $this->_loadUserData();
+ $count -= count($this->users);
+ return $count;
+ }
+
+ /**
+ * Return a count of the number of user which meet $filter criteria
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+ function getUserCount($filter=array()) {
+
+ if($this->users === null) $this->_loadUserData();
+
+ if (!count($filter)) return count($this->users);
+
+ $count = 0;
+ $this->_constructPattern($filter);
+
+ foreach ($this->users as $user => $info) {
+ $count += $this->_filter($user, $info);
+ }
+
+ return $count;
+ }
+
+ /**
+ * Bulk retrieval of user data
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param start index of first user to be returned
+ * @param limit max number of users to be returned
+ * @param filter array of field/pattern pairs
+ * @return array of userinfo (refer getUserData for internal userinfo details)
+ */
+ function retrieveUsers($start=0,$limit=0,$filter=array()) {
+
+ if ($this->users === null) $this->_loadUserData();
+
+ ksort($this->users);
+
+ $i = 0;
+ $count = 0;
+ $out = array();
+ $this->_constructPattern($filter);
+
+ foreach ($this->users as $user => $info) {
+ if ($this->_filter($user, $info)) {
+ if ($i >= $start) {
+ $out[$user] = $info;
+ $count++;
+ if (($limit > 0) && ($count >= $limit)) break;
+ }
+ $i++;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Only valid pageid's (no namespaces) for usernames
+ */
+ function cleanUser($user){
+ global $conf;
+ return cleanID(str_replace(':',$conf['sepchar'],$user));
+ }
+
+ /**
+ * Only valid pageid's (no namespaces) for groupnames
+ */
+ function cleanGroup($group){
+ global $conf;
+ return cleanID(str_replace(':',$conf['sepchar'],$group));
+ }
+
+ /**
+ * Load all user data
+ *
+ * loads the user file into a datastructure
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _loadUserData(){
+ global $config_cascade;
+
+ $this->users = array();
+
+ if(!@file_exists($config_cascade['plainauth.users']['default'])) return;
+
+ $lines = file($config_cascade['plainauth.users']['default']);
+ foreach($lines as $line){
+ $line = preg_replace('/#.*$/','',$line); //ignore comments
+ $line = trim($line);
+ if(empty($line)) continue;
+
+ $row = explode(":",$line,5);
+ $groups = array_values(array_filter(explode(",",$row[4])));
+
+ $this->users[$row[0]]['pass'] = $row[1];
+ $this->users[$row[0]]['name'] = urldecode($row[2]);
+ $this->users[$row[0]]['mail'] = $row[3];
+ $this->users[$row[0]]['grps'] = $groups;
+ }
+ }
+
+ /**
+ * return 1 if $user + $info match $filter criteria, 0 otherwise
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+ function _filter($user, $info) {
+ // FIXME
+ foreach ($this->_pattern as $item => $pattern) {
+ if ($item == 'user') {
+ if (!preg_match($pattern, $user)) return 0;
+ } else if ($item == 'grps') {
+ if (!count(preg_grep($pattern, $info['grps']))) return 0;
+ } else {
+ if (!preg_match($pattern, $info[$item])) return 0;
+ }
+ }
+ return 1;
+ }
+
+ function _constructPattern($filter) {
+ $this->_pattern = array();
+ foreach ($filter as $item => $pattern) {
+ // $this->_pattern[$item] = '/'.preg_quote($pattern,"/").'/i'; // don't allow regex characters
+ $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i'; // allow regex characters
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/plugins/authplain/plugin.info.txt b/lib/plugins/authplain/plugin.info.txt
new file mode 100644
index 000000000..3273e21d7
--- /dev/null
+++ b/lib/plugins/authplain/plugin.info.txt
@@ -0,0 +1,7 @@
+base authplain
+author
+email
+date
+name auth plugin
+desc Provides authentication against local password storage
+url
diff --git a/lib/plugins/config/settings/extra.class.php b/lib/plugins/config/settings/extra.class.php
index b4e35b1cc..f6adf1c18 100644
--- a/lib/plugins/config/settings/extra.class.php
+++ b/lib/plugins/config/settings/extra.class.php
@@ -43,17 +43,54 @@ if (!class_exists('setting_authtype')) {
class setting_authtype extends setting_multichoice {
function initialize($default,$local,$protected) {
+ global $plugin_controller;
- // populate $this->_choices with a list of available auth mechanisms
- $authtypes = glob(DOKU_INC.'inc/auth/*.class.php');
- $authtypes = preg_replace('#^.*/([^/]*)\.class\.php$#i','$1', $authtypes);
- $authtypes = array_diff($authtypes, array('basic'));
- sort($authtypes);
-
- $this->_choices = $authtypes;
+ // retrive auth types provided by plugins
+ foreach ($plugin_controller->getList('auth') as $plugin) {
+ $this->_choices[] = $plugin;
+ }
parent::initialize($default,$local,$protected);
}
+
+ function update($input) {
+ global $plugin_controller;
+
+ // is an update posible?
+ $mayUpdate = parent::update($input);
+
+ // is it an auth plugin?
+ if (in_array($input, $plugin_controller->getList('auth'))) {
+ // reject disabled plugins
+ if ($plugin_controller->isdisabled($input)) {
+ $this->_error = true;
+ msg('Auth type ' . $input . ' is disabled.');
+ return false;
+ }
+
+ // load the plugin
+ $auth_plugin = $plugin_controller->load('auth', $input);
+
+ // @TODO: throw an error in plugin controller instead of returning null
+ if (is_null($auth_plugin)) {
+ $this->_error = true;
+ msg('Cannot load Auth Plugin "' . $input . '"');
+ return false;
+ }
+
+ // verify proper instanciation (is this really a plugin?) @TODO use instanceof? impement interface?
+ if (is_object($auth_plugin) && !method_exists($auth_plugin, 'getPluginName')) {
+ $this->_error = true;
+ msg('Cannot create Auth Plugin "' . $input . '"');
+ return false;
+ }
+ }
+
+ msg('Successfully changed auth system. Please re-login.');
+ auth_logoff();
+
+ return true;
+ }
}
}