_prefs->get('passwd')) return new _PassUser($UserName); else { // PassUsers override BogoUsers if they exist $_PassUser = new _PassUser($UserName); if ($_PassUser->userExists()) return $_PassUser; } } // User has no password. if (_isBogoUserAllowed()) return $_BogoUser; // Passwords are not allowed, and Bogo is disallowed too. (Only // the admin can sign in). return $ForbiddenUser; } /** * Primary WikiUser function, called by main.php. * * This determines the user's type and returns an appropriate user * object. main.php then querys the resultant object for password * validity as necessary. * * If an _AnonUser object is returned, the user may only browse pages * (and save prefs in a cookie). * * To disable access but provide prefs the global $ForbiddenUser class * is returned. (was previously false) * */ function WikiUser ($UserName = '') { global $ForbiddenUser; //TODO: Check sessionvar for username & save username into //sessionvar (may be more appropriate to do this in main.php). if ($UserName) { $ForbiddenUser = new _ForbiddenUser($UserName); // Found a user name. return _determineAdminUserOrOtherUser($UserName); } elseif (!empty($_SESSION['userid'])) { // Found a user name. $ForbiddenUser = new _ForbiddenUser($_SESSION['userid']); return _determineAdminUserOrOtherUser($_SESSION['userid']); } else { // Check for autologin pref in cookie and possibly upgrade // user object to another type. $_AnonUser = new _AnonUser(); if ($UserName = $_AnonUser->_userid && $_AnonUser->_prefs->get('autologin')) { // Found a user name. $ForbiddenUser = new _ForbiddenUser($UserName); return _determineAdminUserOrOtherUser($UserName); } else { $ForbiddenUser = new _ForbiddenUser(); if (_isAnonUserAllowed()) return $_AnonUser; return $ForbiddenUser; // User must sign in to browse pages. } return $ForbiddenUser; // User must sign in with a password. } /* trigger_error("DEBUG: Note: End of function reached in WikiUser." . " " . "Unexpectedly, an appropriate user class could not be determined."); return $ForbiddenUser; // Failsafe. */ } function WikiUserClassname() { return '_WikiUser'; } function UpgradeUser ($olduser, $user) { if (isa($user,'_WikiUser') and isa($olduser,'_WikiUser')) { // populate the upgraded class with the values from the old object foreach (get_object_vars($user) as $k => $v) { if (!empty($v)) $olduser->$k = $v; } $GLOBALS['request']->_user = $olduser; return $olduser; } else { return false; } } function UserExists ($UserName) { global $request; if (!($user = $request->getUser())) $user = WikiUser($UserName); if (!$user) return false; if ($user->userExists($UserName)) { $request->_user = $user; return true; } elseif ($user = $user->nextClass()) return $user->userExists($UserName); $request->_user = $GLOBALS['ForbiddenUser']; return false; } function CheckPass ($UserName, $Password) { global $request; if (!($user = $request->getUser())) $user = WikiUser($UserName); if (!$user) return false; if ($user->checkPass($Password)) { $request->_user = $user; return true; } elseif ($user = $user->nextClass()) return $user->userExists($Password); $request->_user = $GLOBALS['ForbiddenUser']; return false; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // Base WikiUser class. class _WikiUser { var $_userid = ''; var $_level = WIKIAUTH_FORBIDDEN; var $_prefs = false; var $_HomePagehandle = false; // constructor function _WikiUser($UserName = '') { $this->_userid = $UserName; if ($UserName) { $this->_HomePagehandle = $this->hasHomePage(); } $this->getPreferences(); } function UserName() { if (!empty($this->_userid)) return $this->_userid; } function getPreferences() { trigger_error("DEBUG: Note: undefined _WikiUser class trying to load prefs." . " " . "New subclasses of _WikiUser must override this function."); return false; } function setPreferences($prefs, $id_only) { trigger_error("DEBUG: Note: undefined _WikiUser class trying to save prefs." . " " . "New subclasses of _WikiUser must override this function."); return false; } function userExists() { return $this->hasHomePage(); } function checkPass($submitted_password) { // By definition, an undefined user class cannot sign in. trigger_error("DEBUG: Warning: undefined _WikiUser class trying to sign in." . " " . "New subclasses of _WikiUser must override this function."); return false; } // returns page_handle to user's home page or false if none function hasHomePage() { if ($this->_userid) { if ($this->_HomePagehandle) { return $this->_HomePagehandle; } else { // check db again (maybe someone else created it since // we logged in.) global $request; $this->_HomePagehandle = $request->getPage($this->_userid); return $this->_HomePagehandle; } } // nope return false; } function nextAuthMethod() { if (! $this->_current_method) { $this->_auth_methods = $GLOBALS['USER_AUTH_ORDER']; $this->_current_index = -1; } $this->_current_index++; if ($this->_current_index >= count($this->_auth_methods)) return false; $this->_current_method = $this->_auth_methods[$this->_current_index]; return $this->_current_method; } // upgrade the user object function nextClass() { if ($method = $this->nextAuthMethod()) { $class = "_".$method."PassUser"; if ($user = new $class($this->_userid)) { // prevent from endless recursion. UpgradeUser($this, $user); } return $user; } } function PrintLoginForm (&$request, $args, $fail_message = false, $seperate_page = true) { include_once('lib/Template.php'); // Call update_locale in case the system's default language is not 'en'. // (We have no user pref for lang at this point yet, no one is logged in.) update_locale(DEFAULT_LANGUAGE); $userid = $this->_userid; $require_level = 0; extract($args); // fixme $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level)); $pagename = $request->getArg('pagename'); $nocache = 1; $login = new Template('login', $request, compact('pagename', 'userid', 'require_level', 'fail_message', 'pass_required', 'nocache')); if ($seperate_page) { $top = new Template('html', $request, array('TITLE' => _("Sign In"))); return $top->printExpansion($login); } else { return $login; } } // signed in but probably not password checked function isSignedIn() { return (isa($this,'_BogoUser') or isa($this,'_PassUser')); } function isAuthenticated () { //return isa($this,'_PassUser'); //return isa($this,'_BogoUser') || isa($this,'_PassUser'); return $this->_level >= WIKIAUTH_BOGO; } function isAdmin () { return $this->_level == WIKIAUTH_ADMIN; } function getId () { return ( $this->UserName() ? $this->UserName() : $GLOBALS['request']->get('REMOTE_ADDR') ); // FIXME: globals } function getAuthenticatedId() { return ( $this->isAuthenticated() ? $this->_userid : ''); //$GLOBALS['request']->get('REMOTE_ADDR') ); // FIXME: globals } function hasAuthority ($require_level) { return $this->_level >= $require_level; } function AuthCheck ($postargs) { // Normalize args, and extract. $keys = array('userid', 'passwd', 'require_level', 'login', 'logout', 'cancel'); foreach ($keys as $key) $args[$key] = isset($postargs[$key]) ? $postargs[$key] : false; extract($args); $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level)); if ($logout) { // Log out $GLOBALS['request']->_user = new _AnonUser(); return $GLOBALS['request']->_user; } elseif ($cancel) return false; // User hit cancel button. elseif (!$login && !$userid) return false; // Nothing to do? $authlevel = $this->checkPass($passwd); if (!$authlevel) return _("Invalid password or userid."); elseif ($authlevel < $require_level) return _("Insufficient permissions."); // Successful login. $user = $GLOBALS['request']->_user; $user->_userid = $userid; $user->_level = $authlevel; return $user; } } class _AnonUser extends _WikiUser { var $_level = WIKIAUTH_ANON; // Anon only gets to load and save prefs in a cookie, that's it. function getPreferences() { global $request; if (empty($this->_prefs)) $this->_prefs = new UserPreferences; $UserName = $this->UserName(); if ($cookie = $request->getCookieVar(WIKI_NAME)) { if (! $unboxedcookie = $this->_prefs->retrieve($cookie)) { trigger_error(_("Format of UserPreferences cookie not recognised.") . " " . _("Default preferences will be used."), E_USER_WARNING); } // TODO: try reading userid from old PhpWiki cookie // formats, then delete old cookie from browser! // //else { // try old cookie format. //$cookie = $request->getCookieVar('WIKI_ID'); //} /** * Only keep the cookie if it matches the UserName who is * signing in or if this really is an Anon login (no * username). (Remember, _BogoUser and higher inherit this * function too!). */ if (! $UserName || $UserName == $unboxedcookie['userid']) { $this->_prefs = new UserPreferences($unboxedcookie); $this->_userid = $unboxedcookie['userid']; } } // initializeTheme() needs at least an empty object if (! $this->_prefs ) $this->_prefs = new UserPreferences; return $this->_prefs; } function setPreferences($prefs, $id_only=false) { // Allow for multiple wikis in same domain. Encode only the // _prefs array of the UserPreference object. Ideally the // prefs array should just be imploded into a single string or // something so it is completely human readable by the end // user. In that case stricter error checking will be needed // when loading the cookie. setcookie(WIKI_NAME, $this->_prefs->store(), COOKIE_EXPIRATION_DAYS, COOKIE_DOMAIN); } function userExists() { return true; } function checkPass($submitted_password) { // By definition, the _AnonUser does not HAVE a password // (compared to _BogoUser, who has an EMPTY password). trigger_error("DEBUG: Warning: _AnonUser unexpectedly asked to checkPass()." . " " . "Check isa(\$user, '_PassUser'), or: isa(\$user, '_AdminUser') etc. first." . " " . "New subclasses of _WikiUser must override this function."); return false; } } class _ForbiddenUser extends _AnonUser { var $_level = WIKIAUTH_FORBIDDEN; function checkPass($submitted_password) { return false; } function userExists() { if ($this->_HomePagehandle) return true; return false; } } /** * Do NOT extend _BogoUser to other classes, for checkPass() * security. (In case of defects in code logic of the new class!) */ class _BogoUser extends _AnonUser { function userExists() { if (isWikiWord($this->_userid)) { $this->_level = WIKIAUTH_BOGO; return true; } else { $this->_level = WIKIAUTH_ANON; return false; } } function checkPass($submitted_password) { // By definition, BogoUser has an empty password. $this->userExists(); return $this->_level; } } class _PassUser extends _AnonUser /** * Called if ALLOW_USER_PASSWORDS and Anon and Bogo failed. * * The classes for all subsequent auth methods extend from * this class. * This handles the auth method type dispatcher according $USER_AUTH_ORDER, * the three auth method policies first-only, strict and stacked * and the two methods for prefs: homepage or database, * if $DBAuthParams['pref_select'] is defined. * * Default is PersonalPage auth and prefs. * */ { var $_auth_dbi, $_prefmethod, $_prefselect, $_prefupdate; var $_current_method, $_current_index; // check and prepare the auth and pref methods only once function _PassUser($UserName = '') { global $DBAuthParams; if ($UserName) { $this->_userid = $UserName; $this->_HomePagehandle = $this->hasHomePage(); } // Check the configured Prefs methods if (!empty($DBAuthParams['pref_select']) and $DBParams['dbtype'] == 'SQL') { $this->_prefmethod = 'SQL'; // really pear db $dbh = $this->getAuthDbh(); // preparate the SELECT statement $this->_prefselect = $dbh->_backend->prepare( preg_replace('"$userid"','?',$DBAuthParams['pref_select']) ); } if (!empty($DBAuthParams['pref_select']) and $DBParams['dbtype'] == 'ADODB') { $this->_prefmethod = 'ADODB'; // uses a simplier execute syntax $dbh = $this->getAuthDbh(); // preparate the SELECT statement $this->_prefselect = preg_replace('"$userid"','%s',$DBAuthParams['pref_select']); } if (!empty($DBAuthParams['pref_update']) and $DBParams['dbtype'] == 'SQL') { $this->_prefmethod = 'SQL'; $dbh = $this->getAuthDbh(); $this->_prefupdate = $dbh->_backend->prepare( preg_replace(array('"$userid"','"$pref_blob"'),array('?','?'),$DBAuthParams['pref_update']) ); } if (!empty($DBAuthParams['pref_update']) and $DBParams['dbtype'] == 'ADODB') { $this->_prefmethod = 'ADODB'; // uses a simplier execute syntax $dbh = $this->getAuthDbh(); // preparate the SELECT statement $this->_prefupdate = preg_replace(array('"$userid"','"$pref_blob"'),array('%s','%s'),$DBAuthParams['pref_update']); } $this->getPreferences(); // Upgrade to the next parent _PassUser class. Avoid recursion. if ( get_class($this) === '_PassUser' ) { //Auth policy: Check the order of the configured auth methods // 1. first-only: Upgrade the class here in the constructor // 2. old: ignore USER_AUTH_ORDER and try to use all available methods as in the previous PhpWiki releases (slow) // 3. strict: upgrade the class after checking the user existance in userExists() // 4. stacked: upgrade the class after the password verification in checkPass() // Methods: PersonalPage, HTTP_AUTH, DB, LDAP, IMAP, File if (!defined('USER_AUTH_POLICY')) define('USER_AUTH_POLICY','old'); if (defined('USER_AUTH_POLICY')) { // policy 1: only pre-define one method for all users if (USER_AUTH_POLICY === 'first-only') { if ($user = $this->nextClass()) return $user; else return $GLOBALS['ForbiddenUser']; } // use the default behaviour from the previous versions: elseif (USER_AUTH_POLICY === 'old') { // default: try to be smart if (!empty($GLOBALS['PHP_AUTH_USER'])) { return new _HttpAuthUserClass($UserName); } elseif (!empty($DBAuthParams['auth_check']) and ($DBAuthParams['auth_dsn'] or $GLOBALS ['DBParams']['dsn'])) { return new _DbUserClass($UserName); } elseif (defined('LDAP_AUTH_HOST') and defined('LDAP_AUTH_SEARCH') and function_exists('ldap_open')) { return new _LDAPUserClass($UserName); } elseif (defined('IMAP_AUTH_HOST') and function_exists('imap_open')) { return new _IMAPUserClass($UserName); } elseif (defined('AUTH_USER_FILE')) { return new _FileUserClass($UserName); } else { return new _PersonalPageUserClass($UserName); } } else // else use the page methods defined in _PassUser. return $this; } } } function getAuthDbh () { global $request, $DBParams, $DBAuthParams; if (empty($this->_auth_dbi)) { if ($DBParams['dbtype'] == 'dba' or empty($DBAuthParams['auth_dsn'])) $this->_auth_dbi = $request->getDbh(); // use phpwiki database elseif ($DBAuthParams['auth_dsn'] == $DBParams['dsn']) $this->_auth_dbi = $request->getDbh(); // same phpwiki database else // use another external database handle. needs PHP 4.1 $this->_auth_dbi = WikiDB::open($DBAuthParams); } return $this->_auth_dbi; } //TODO: password changing //TODO: email verification function getPreferences() { // We don't necessarily have to read the cookie first. Since // the user has a password, the prefs stored in the homepage // cannot be arbitrarily altered by other Bogo users. _AnonUser::getPreferences(); // database prefs if ((! $this->_prefs) && $this->_prefselect) { if ($this->_prefmethod == 'ADODB') { $dbh = & $this->_auth_dbi; $db_result = $dbh->_backend->Execute($this->_prefselect,$dbh->qstr($this->_userid)); if (!$rs->EOF) $prefs_blob = $db_result->fields['pref_blob']; $db_result->Close(); } else { // pear db methods $db_result = $this->_auth_dbi->_backend->execute($this->_prefselect,$this->_userid); list($prefs_blob) = $db_result->fetchRow(); } if ($restored_from_db = $this->_prefs->retrieve($prefs_blob)) { $this->_prefs = new UserPreferences($restored_from_db); return $this->_prefs; } } // User may have deleted cookie, retrieve from his // PersonalPage if there is one. if ((! $this->_prefs) && $this->_HomePagehandle) { if ($restored_from_page = $this->_prefs->retrieve($this->_HomePagehandle->get('pref'))) { $this->_prefs = new UserPreferences($restored_from_page); return $this->_prefs; } } return $this->_prefs; } function setPreferences($prefs, $id_only=false) { _AnonUser::setPreferences($prefs, $id_only); // Encode only the _prefs array of the UserPreference object $serialized = $this->_prefs->store(); // database prefs if ((! $this->_prefs) && $this->_prefupdate) { if ($this->_prefmethod == 'ADODB') { $dbh =& $this->_auth_dbi; $db_result = $dbh->_backend->Execute($this->_prefupdate,$dbh->qstr($serialized),$dbh->qstr($this->_userid)); $db_result->Close(); } else { // pear db methods $db_result = $this->_auth_dbi->_backend->execute($this->_prefupdate,$serialized,$this->_userid); } } else $this->_HomePagehandle->set('pref', $serialized); } function mayChangePass() { return true; } //The default method is getting the password from prefs. // child methods obtain $stored_password from external auth. function userExists() { //if ($this->_HomePagehandle) return true; if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) return $user->userExists(); } return false; } //The default method is getting the password from prefs. // child methods obtain $stored_password from external auth. function checkPass($submitted_password) { $stored_password = $this->_prefs->get('passwd'); $result = $this->_checkPass($submitted_password, $stored_password); if ($result) $this->_level = WIKIAUTH_USER; if (!$result) { if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) { if ($user = $user->userExists()) return $user->checkPass($submitted_password); } } if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') { if ($user = $this->nextClass()) return $user->checkPass($submitted_password); } } return $this->_level; } //TODO: remove crypt() function check from config.php:396 ?? function _checkPass($submitted_password, $stored_password) { if(!empty($submitted_password)) { if (defined('ENCRYPTED_PASSWD') && ENCRYPTED_PASSWD) { // Verify against encrypted password. if (function_exists('crypt')) { if (crypt($submitted_password, $stored_password) == $stored_password ) return true; // matches encrypted password else return false; } else { trigger_error(_("The crypt function is not available in this version of PHP.") . " " . _("Please set ENCRYPTED_PASSWD to false in index.php and change ADMIN_PASSWD."), E_USER_WARNING); return false; } } else { // Verify against cleartext password. if ($submitted_password == $stored_password) return true; else { // Check whether we forgot to enable ENCRYPTED_PASSWD if (function_exists('crypt')) { if (crypt($submitted_password, $stored_password) == $stored_password) { trigger_error(_("Please set ENCRYPTED_PASSWD to true in index.php."), E_USER_WARNING); return true; } } } } } return false; } } class _BogoLoginUser extends _PassUser { function userExists() { if (isWikiWord($this->_userid)) { $this->_level = WIKIAUTH_BOGO; return true; } else { $this->_level = WIKIAUTH_ANON; return false; } } function checkPass($submitted_password) { // A BogoLoginUser requires PASSWORD_LENGTH_MINIMUM. if ($this->userExists()) return $this->_level; } } class _PersonalPagePassUser extends _PassUser /** * This class is only to simplify the auth method dispatcher. * It inherits all methods from _PassUser. */ { function dummy() {} } class _DbPassUser extends _PassUser /** * Authenticate against a database, to be able to use shared users. * internal: no different $DbAuthParams['dsn'] defined, or * external: different $DbAuthParams['dsn'] * The magic is done in the symbolic SQL statements in index.php * * We support only the SQL and ADODB backends. * The other WikiDB backends (flat, cvs, dba, ...) should be used for pages, * not for auth stuff. If one would like to use e.g. dba for auth, he should * use PearDB (SQL) with the right $DBAuthParam['auth_dsn']. * Flat files for auth use is handled by the auth method "File". * * Preferences are handled in the parent class. */ { var $_authselect, $_authupdate; // This can only be called from _PassUser, because the parent class // sets the auth_dbi and pref methods, before this class is initialized. function _DbPassUser() { if (!$this->_prefs) { trigger_error(__vsprintf("Internal error: %s may only called from %s","_DbPassUser","_PassUser"), E_USER_WARNING); return false; } $this->_authmethod = 'DB'; $this->_auth_dbi = $this->getAuthDbh(); $this->_auth_crypt_method = $GLOBALS['DBAuthParams']['auth_crypt_method']; if ($GLOBALS['DBParams']['dbtype'] == 'ADODB') return new _AdoDbPassUser(); else return new _PearDbPassUser(); } function mayChangePass() { return !empty($this->_authupdate); } } class _PearDbPassUser extends _DbPassUser /** * Pear DB methods */ { function _PearDbPassUser() { global $DBAuthParams; if (!$this->_prefs) { trigger_error(__vsprintf("Internal error: %s may only called from %s","_PearDbPassUser","_DbPassUser"), E_USER_WARNING); return false; } // Prepare the configured auth statements if (!empty($DBAuthParams['auth_check'])) { $this->_authselect = $this->_auth_dbi->_backend->prepare ( preg_replace(array('"$userid"','"$password"'),array('?','?'), $DBAuthParams['auth_check']) ); } if (!empty($DBAuthParams['auth_update'])) { $this->_authupdate = $this->_auth_dbi->_backend->prepare( preg_replace(array('"$userid"','"$password"'),array('?','?'),$DBAuthParams['auth_update']) ); } } function getPreferences() { // override the generic slow method here for efficiency and not to // clutter the homepage metadata with prefs. _AnonUser::getPreferences(); if ((! $this->_prefs) && $this->_prefselect) { $db_result = $this->_auth_dbi->_backend->execute($this->_prefselect,$this->_userid); list($prefs_blob) = $db_result->fetchRow(); if ($restored_from_db = $this->_prefs->retrieve($prefs_blob)) { $this->_prefs = new UserPreferences($restored_from_db); return $this->_prefs; } } if ((! $this->_prefs) && $this->_HomePagehandle) { if ($restored_from_page = $this->_prefs->retrieve($this->_HomePagehandle->get('pref'))) { $this->_prefs = new UserPreferences($restored_from_page); return $this->_prefs; } } return $this->_prefs; } function setPreferences($prefs, $id_only=false) { $serialized = $this->_prefs->store(); if ($this->_prefupdate) { $db_result = $this->_auth_dbi->_backend->execute($this->_prefupdate,$serialized,$this->_userid); } else { _AnonUser::setPreferences($prefs, $id_only); $this->_HomePagehandle->set('pref', $serialized); } } function userExists() { if (!$this->_authselect) trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'SQL'", E_USER_WARNING); $dbh = &$this->_auth_dbi; //NOTE: for auth_crypt_method='crypt' no special auth_user_exists is needed if ($this->_auth_crypt_method == 'crypt') { $rs = $dbh->_backend->Execute($this->_authselect,$this->_userid) ; if ($rs->numRows()) return true; } else { if (! $GLOBALS['DBAuthParams']['auth_user_exists']) trigger_error("\$DBAuthParams['auth_user_exists'] is missing", E_USER_WARNING); $this->_authcheck = preg_replace('/"$userid"/','? ', $GLOBALS['DBAuthParams']['auth_user_exists']); $rs = $dbh->_backend->Execute($this->_authcheck, $this->_userid) ; if ($rs->numRows()) return true; } if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) { return $user->userExists(); } } } function checkPass($submitted_password) { if (!$this->_authselect) trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'SQL'", E_USER_WARNING); //NOTE: for auth_crypt_method='crypt' defined('ENCRYPTED_PASSWD',true) must be set if ($this->_auth_crypt_method == 'crypt') { $db_result = $this->_auth_dbi->_backend->execute($this->_authselect,$this->_userid); list($stored_password) = $db_result->fetchRow(); $result = $this->_checkPass($submitted_password, $stored_password); } else { $db_result = $this->_auth_dbi->_backend->execute($this->_authselect,$submitted_password,$this->_userid); list($okay) = $db_result->fetchRow(); $result = !empty($okay); } if ($result) { $this->_level = WIKIAUTH_USER; } else { if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) { if ($user = $user->userExists()) return $user->checkPass($submitted_password); } } if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') { if ($user = $this->nextClass()) return $user->checkPass($submitted_password); } } return $this->_level; } function storePass($submitted_password) { if (!$this->_authupdate) { //CHECKME trigger_warning("Either \$DBAuthParams['auth_update'] not defined or \$DBParams['dbtype'] != 'SQL'", E_USER_WARNING); return false; } if ($this->_auth_crypt_method == 'crypt') { if (function_exists('crypt')) $submitted_password = crypt($submitted_password); } $db_result = $this->_auth_dbi->_backend->execute($this->_authupdate,$submitted_password,$this->_userid); } } class _AdoDbPassUser extends _DbPassUser /** * ADODB methods */ { function _AdoDbPassUser() { global $DBAuthParams; if (!$this->_prefs) { trigger_error(__vsprintf("Internal error: %s may only called from %s","_AdoDbPassUser","_DbPassUser"), E_USER_WARNING); return false; } // Prepare the configured auth statements if (!empty($DBAuthParams['auth_check'])) { $this->_authselect = preg_replace(array('"$userid"','"$password"'),array('%s','%s'), $DBAuthParams['auth_check']); } if (!empty($DBAuthParams['auth_update'])) { $this->_authupdate = preg_replace(array('"$userid"','"$password"'),array('%s','%s'), $DBAuthParams['auth_update']); } } function getPreferences() { // override the generic slow method here for efficiency _AnonUser::getPreferences(); if ((! $this->_prefs) && $this->_prefselect) { $dbh = & $this->_auth_dbi; $rs = $dbh->_backend->Execute($this->_prefselect,$dbh->qstr($this->_userid)); if ($rs->EOF) { $rs->Close(); } else { $prefs_blob = $rs->fields['pref_blob']; $rs->Close(); if ($restored_from_db = $this->_prefs->retrieve($prefs_blob)) { $this->_prefs = new UserPreferences($restored_from_db); return $this->_prefs; } } } if ((! $this->_prefs) && $this->_HomePagehandle) { if ($restored_from_page = $this->_prefs->retrieve($this->_HomePagehandle->get('pref'))) { $this->_prefs = new UserPreferences($restored_from_page); return $this->_prefs; } } return $this->_prefs; } function setPreferences($prefs, $id_only=false) { $serialized = $this->_prefs->store(); if ($this->_prefupdate) { $dbh = & $this->_auth_dbi; $db_result = $dbh->_backend->Execute($this->_prefupdate,$dbh->qstr($serialized),$dbh->qstr($this->_userid)); $db_result->Close(); } else { _AnonUser::setPreferences($prefs, $id_only); $this->_HomePagehandle->set('pref', $serialized); } } function userExists() { if (!$this->_authselect) trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'ADODB'", E_USER_WARNING); $dbh = &$this->_auth_dbi; //NOTE: for auth_crypt_method='crypt' no special auth_user_exists is needed if ($this->_auth_crypt_method == 'crypt') { $rs = $dbh->_backend->Execute($this->_authselect,$dbh->qstr($this->_userid)); if (!$rs->EOF) { $rs->Close(); return true; } else { $rs->Close(); } } else { if (! $GLOBALS['DBAuthParams']['auth_user_exists']) trigger_error("\$DBAuthParams['auth_user_exists'] is missing", E_USER_WARNING); $this->_authcheck = preg_replace('/"$userid"/','%s', $GLOBALS['DBAuthParams']['auth_user_exists']); $rs = $dbh->_backend->Execute($this->_authcheck, $dbh->qstr($this->_userid)); if (!$rs->EOF) { $rs->Close(); return true; } else { $rs->Close(); } } if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) return $user->userExists(); } return false; } function checkPass($submitted_password) { if (!$this->_authselect) trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'ADODB'", E_USER_WARNING); $dbh = &$this->_auth_dbi; //NOTE: for auth_crypt_method='crypt' defined('ENCRYPTED_PASSWD',true) must be set if ($this->_auth_crypt_method == 'crypt') { $rs = $dbh->_backend->Execute($this->_authselect,$dbh->qstr($this->_userid)); if (!$rs->EOF) { $stored_password = $rs->fields['password']; $rs->Close(); $result = $this->_checkPass($submitted_password, $stored_password); } else { $rs->Close(); $result = false; } } else { $rs = $dbh->_backend->Execute($this->_authselect, $dbh->qstr($submitted_password), $dbh->qstr($this->_userid)); $okay = $rs->fields['ok']; $rs->Close(); $result = !empty($okay); } if ($result) { $this->_level = WIKIAUTH_USER; } else { if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) { if ($user = $user->userExists()) return $user->checkPass($submitted_password); } } if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') { if ($user = $this->nextClass()) return $user->checkPass($submitted_password); } } return $this->_level; } function storePass($submitted_password) { if (!$this->_authupdate) { //CHECKME trigger_warning("Either \$DBAuthParams['auth_update'] not defined or \$DBParams['dbtype'] != 'ADODB'", E_USER_WARNING); return false; } if ($this->_auth_crypt_method == 'crypt') { if (function_exists('crypt')) $submitted_password = crypt($submitted_password); } $dbh = &$this->_auth_dbi; $rs = $dbh->_backend->Execute($this->_authupdate, $dbh->qstr($submitted_password), $dbh->qstr($this->_userid)); $rs->Close(); } } class _LDAPPassUser extends _PassUser /** * Define the vars LDAP_HOST and LDAP_AUTH_SEARCH in index.php * * Preferences are handled in _PassUser */ { function checkPass($submitted_password) { $this->_authmethod = 'LDAP'; $userid = $this->_userid; if ($ldap = ldap_connect(LDAP_AUTH_HOST)) { // must be a valid LDAP server! $r = @ldap_bind($ldap); // this is an anonymous bind $st_search = "uid=$userid"; // Need to set the right root search information. see ../index.php $sr = ldap_search($ldap, LDAP_AUTH_SEARCH, "$st_search"); $info = ldap_get_entries($ldap, $sr); // there may be more hits with this userid. try every for ($i = 0; $i < $info["count"]; $i++) { $dn = $info[$i]["dn"]; // The password is still plain text. if ($r = @ldap_bind($ldap, $dn, $passwd)) { // ldap_bind will return TRUE if everything matches ldap_close($ldap); $this->_level = WIKIAUTH_USER; return $this->_level; } } } else { trigger_error(_("Unable to connect to LDAP server "). LDAP_AUTH_HOST, E_USER_WARNING); } if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) { if ($user = $user->userExists()) return $user->checkPass($submitted_password); } } if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') { if ($user = $this->nextClass()) return $user->checkPass($submitted_password); } return false; } function userExists() { $userid = $this->_userid; if ($ldap = ldap_connect(LDAP_AUTH_HOST)) { // must be a valid LDAP server! $r = @ldap_bind($ldap); // this is an anonymous bind $st_search = "uid=$userid"; // Need to set the right root search information. see ../index.php $sr = ldap_search($ldap, LDAP_AUTH_SEARCH, "$st_search"); $info = ldap_get_entries($ldap, $sr); // there may be more hits with this userid. try every if ($info["count"]) { ldap_close($ldap); return true; } } else { trigger_error(_("Unable to connect to LDAP server "). LDAP_AUTH_HOST, E_USER_WARNING); } if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) return $user->userExists(); } return false; } function mayChangePass() { return false; } } class _IMAPPassUser extends _PassUser /** * Define the var IMAP_HOST in index.php * * Preferences are handled in _PassUser */ { function checkPass($submitted_password) { $userid = $this->_userid; $mbox = @imap_open( "{" . IMAP_AUTH_HOST . "}", $userid, $submitted_password, OP_HALFOPEN ); if ($mbox) { imap_close($mbox); $this->_authmethod = 'IMAP'; $this->_level = WIKIAUTH_USER; return $this->_level; } else { trigger_error(_("Unable to connect to IMAP server "). IMAP_AUTH_HOST, E_USER_WARNING); } if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) { if ($user = $user->userExists()) return $user->checkPass($submitted_password); } } if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') { if ($user = $this->nextClass()) return $user->checkPass($submitted_password); } return false; } //CHECKME: this will not be okay for the auth policy strict function userExists() { if (checkPass($this->_prefs->get('passwd'))) return true; if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) return $user->userExists(); } } function mayChangePass() { return false; } } class _FilePassUser extends _PassUser /** * Check users defined in a .htaccess style file * username:crypt\n... * * Preferences are handled in _PassUser */ { var $_file, $_may_change; // This can only be called from _PassUser, because the parent class // sets the pref methods, before this class is initialized. function _FilePassUser($file = '') { global $DBAuthParams; if (!$this->_prefs) { trigger_error(__vsprintf("Internal error: %s may only called from %s","_FilePassUser","_PassUser"), E_USER_WARNING); return false; } // read the .htaccess style file. We use our own copy of the standard pear class. require 'lib/pear/File_Passwd.php'; // if passwords may be changed we have to lock them: $this->_may_change = defined('AUTH_USER_FILE_STORABLE') && AUTH_USER_FILE_STORABLE; if (empty($file) and defined('AUTH_USER_FILE')) $this->_file = AUTH_USER_FILE; elseif (!empty($file)) $this->_file = File_Passwd($file, !empty($this->_may_change)); else return false; return $this; } function mayChangePass() { return $this->_may_change; } function userExists() { if (isset($this->_file->users[$this->_userid])) return true; if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) return $user->userExists(); } } function checkPass($submitted_password) { if ($this->_file->verifyPassword($this->_userid,$submitted_password)) { $this->_authmethod = 'File'; $this->_level = WIKIAUTH_USER; return $this->_level; } if (USER_AUTH_POLICY === 'strict') { if ($user = $this->nextClass()) { if ($user = $user->userExists()) return $user->checkPass($submitted_password); } } if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') { if ($user = $this->nextClass()) return $user->checkPass($submitted_password); } return false; } function storePass($submitted_password) { if ($this->_may_change) return $this->_file->modUser($this->_userid,$submitted_password); else return false; } } /** * Insert more auth classes here... * */ /** * For security, this class should not be extended. Instead, extend * from _PassUser (think of this as unix "root"). */ class _AdminUser extends _PassUser { //var $_level = WIKIAUTH_ADMIN; function checkPass($submitted_password) { $stored_password = ADMIN_PASSWD; if ($this->_checkPass($submitted_password, $stored_password)) { $this->_level = WIKIAUTH_ADMIN; return $this->_level; } else { $this->_level = WIKIAUTH_ANON; return false; } } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Various data classes for the preference types, * to support get, set, sanify (range checking, ...) * update() will do the neccessary side-effects if a * setting gets changed (theme, language, ...) */ class _UserPreference { var $default_value; function _UserPreference ($default_value) { $this->default_value = $default_value; } function sanify ($value) { return (string)$value; } function get ($name) { if (isset($this->$name)) return $this->{$name}; else return $this->default_value; } function getraw ($name) { if (!empty($this->{$name})) return $this->{$name}; } // stores the value as $this->$name, and not as $this->value (clever?) function set ($name, $value) { if ($value != $this->default_value) $this->$name = $value; else unset($this->$name); } // default: no side-effects function update ($value) { } } class _UserPreference_numeric extends _UserPreference { function _UserPreference_numeric ($default, $minval = false, $maxval = false) { $this->_UserPreference((double)$default); $this->_minval = (double)$minval; $this->_maxval = (double)$maxval; } function sanify ($value) { $value = (double)$value; if ($this->_minval !== false && $value < $this->_minval) $value = $this->_minval; if ($this->_maxval !== false && $value > $this->_maxval) $value = $this->_maxval; return $value; } } class _UserPreference_int extends _UserPreference_numeric { function _UserPreference_int ($default, $minval = false, $maxval = false) { $this->_UserPreference_numeric((int)$default, (int)$minval, (int)$maxval); } function sanify ($value) { return (int)parent::sanify((int)$value); } } class _UserPreference_bool extends _UserPreference { function _UserPreference_bool ($default = false) { $this->_UserPreference((bool)$default); } function sanify ($value) { if (is_array($value)) { /* This allows for constructs like: * * * * * (If the checkbox is not checked, only the hidden input * gets sent. If the checkbox is sent, both inputs get * sent.) */ foreach ($value as $val) { if ($val) return true; } return false; } return (bool) $value; } } class _UserPreference_language extends _UserPreference { function _UserPreference_language ($default = DEFAULT_LANGUAGE) { $this->_UserPreference($default); } // FIXME: check for valid locale function sanify ($value) { // Revert to DEFAULT_LANGUAGE if user does not specify // language in UserPreferences or chooses . if ($value == '' or empty($value)) $value = DEFAULT_LANGUAGE; return (string) $value; } } class _UserPreference_theme extends _UserPreference { function _UserPreference_theme ($default = THEME) { $this->_UserPreference($default); } function sanify ($value) { if (file_exists($this->_themefile($value))) return $value; return $this->default_value; } function update ($newvalue) { global $Theme; include_once($this->_themefile($newvalue)); if (empty($Theme)) include_once($this->_themefile(THEME)); } function _themefile ($theme) { return "themes/$theme/themeinfo.php"; } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * UserPreferences * * This object holds the $request->_prefs subobjects. * A simple packed array of non-default values get's stored as cookie, * homepage, and database, which are converted to the array of * ->_prefs objects. * We don't store the objects, because otherwise we will * not be able to upgrade any subobject. And it's a waste of space also. */ class UserPreferences { function UserPreferences ($saved_prefs = false) { // userid stored too, to ensure the prefs are being loaded for // the correct (currently signing in) userid if stored in a // cookie. $this->_prefs = array( 'userid' => new _UserPreference(''), 'passwd' => new _UserPreference(''), 'autologin' => new _UserPreference_bool(), 'email' => new _UserPreference(''), 'emailVerified' => new _UserPreference_bool(), 'notifyPages' => new _UserPreference(''), 'theme' => new _UserPreference_theme(THEME), 'lang' => new _UserPreference_language(DEFAULT_LANGUAGE), 'editWidth' => new _UserPreference_int(EDITWIDTH_DEFAULT_COLS, EDITWIDTH_MIN_COLS, EDITWIDTH_MAX_COLS), 'noLinkIcons' => new _UserPreference_bool(), 'editHeight' => new _UserPreference_int(EDITHEIGHT_DEFAULT_ROWS, EDITHEIGHT_MIN_ROWS, EDITHEIGHT_DEFAULT_ROWS), 'timeOffset' => new _UserPreference_numeric(TIMEOFFSET_DEFAULT_HOURS, TIMEOFFSET_MIN_HOURS, TIMEOFFSET_MAX_HOURS), 'relativeDates' => new _UserPreference_bool() ); if (is_array($saved_prefs)) { foreach ($saved_prefs as $name => $value) $this->set($name, $value); } } function _getPref ($name) { if (!isset($this->_prefs[$name])) { if ($name == 'passwd2') return false; trigger_error("$name: unknown preference", E_USER_NOTICE); return false; } return $this->_prefs[$name]; } // get the value or default_value of the subobject function get ($name) { if ($_pref = $this->_getPref($name)) return $_pref->get($name); else return false; /* if (is_object($this->_prefs[$name])) return $this->_prefs[$name]->get($name); elseif (($value = $this->_getPref($name)) === false) return false; elseif (!isset($value)) return $this->_prefs[$name]->default_value; else return $value; */ } // check and set the new value in the subobject function set ($name, $value) { $pref = $this->_getPref($name); if ($pref === false) return false; /* do it here or outside? */ if ($name == 'passwd' and defined('PASSWORD_LENGTH_MINIMUM') and strlen($value) <= PASSWORD_LENGTH_MINIMUM ) { //TODO: How to notify the user? return false; } $newvalue = $pref->sanify($value); $oldvalue = $pref->get($name); // update on changes if ($newvalue != $oldvalue) $pref->update($newvalue); return true; // don't set default values to save space (in cookies, db and // sesssion) /* if ($value == $pref->default_value) unset($this->_prefs[$name]); else $this->_prefs[$name] = $pref; */ } // array of objects => array of values function store() { $prefs = array(); foreach ($this->_prefs as $name => $object) { if ($value = $object->getraw($name)) $prefs[] = array($name => $value); } return $this->pack($prefs); } // packed string or array of values => array of values function retrieve($packed) { if (is_string($packed) and (substr($packed, 0, 2) == "a:")) $packed = unserialize($packed); if (!is_array($packed)) return false; $prefs = array(); foreach ($packed as $name => $packed_pref) { if (substr($packed_pref, 0, 2) == "O:") { //legacy: check if it's an old array of objects // Looks like a serialized object. // This might fail if the object definition does not exist anymore. // object with ->$name and ->default_value vars. $pref = unserialize($packed_pref); $prefs[$name] = $pref->get($name); } else { $prefs[$name] = unserialize($packed_pref); } } return $prefs; } // array of objects function getAll() { return $this->_prefs; } function pack($nonpacked) { return serialize($nonpacked); } function unpack($packed) { if (!$packed) return false; if (substr($packed, 0, 2) == "O:") { // Looks like a serialized object return unserialize($packed); } //trigger_error("DEBUG: Can't unpack bad UserPreferences", //E_USER_WARNING); return false; } function hash () { return hash($this->_prefs); } } // $Log: not supported by cvs2svn $ // Revision 1.6 2004/01/26 09:17:49 rurban // * changed stored pref representation as before. // the array of objects is 1) bigger and 2) // less portable. If we would import packed pref // objects and the object definition was changed, PHP would fail. // This doesn't happen with an simple array of non-default values. // * use $prefs->retrieve and $prefs->store methods, where retrieve // understands the interim format of array of objects also. // * simplified $prefs->get() and fixed $prefs->set() // * added $user->_userid and class '_WikiUser' portability functions // * fixed $user object ->_level upgrading, mostly using sessions. // this fixes yesterdays problems with loosing authorization level. // * fixed WikiUserNew::checkPass to return the _level // * fixed WikiUserNew::isSignedIn // * added explodePageList to class PageList, support sortby arg // * fixed UserPreferences for WikiUserNew // * fixed WikiPlugin for empty defaults array // * UnfoldSubpages: added pagename arg, renamed pages arg, // removed sort arg, support sortby arg // // Revision 1.5 2004/01/25 03:05:00 rurban // First working version, but has some problems with the current main loop. // Implemented new auth method dispatcher and policies, all the external // _PassUser classes (also for ADODB and Pear DB). // The two global funcs UserExists() and CheckPass() are probably not needed, // since the auth loop is done recursively inside the class code, upgrading // the user class within itself. // Note: When a higher user class is returned, this doesn't mean that the user // is authorized, $user->_level is still low, and only upgraded on successful // login. // // Revision 1.4 2003/12/07 19:29:48 carstenklapp // Code Housecleaning: fixed syntax errors. (php -l *.php) // // Revision 1.3 2003/12/06 19:10:46 carstenklapp // Finished off logic for determining user class, including // PassUser. Removed ability of BogoUser to save prefs into a page. // // Revision 1.2 2003/12/03 21:45:48 carstenklapp // Added admin user, password user, and preference classes. Added // password checking functions for users and the admin. (Now the easy // parts are nearly done). // // Revision 1.1 2003/12/02 05:46:36 carstenklapp // Complete rewrite of WikiUser.php. // // This should make it easier to hook in user permission groups etc. some // time in the future. Most importantly, to finally get UserPreferences // fully working properly for all classes of users: AnonUser, BogoUser, // AdminUser; whether they have a NamesakePage (PersonalHomePage) or not, // want a cookie or not, and to bring back optional AutoLogin with the // UserName stored in a cookie--something that was lost after PhpWiki had // dropped the default http auth login method. // // Added WikiUser classes which will (almost) work together with existing // UserPreferences class. Other parts of PhpWiki need to be updated yet // before this code can be hooked up. // // Local Variables: // mode: php // tab-width: 8 // c-basic-offset: 4 // c-hanging-comment-ender-p: nil // indent-tabs-mode: nil // End: ?>