diff --git a/source/lib/SihnonFramework/Auth.class.php b/source/lib/SihnonFramework/Auth.class.php index 383168b..8af12bc 100644 --- a/source/lib/SihnonFramework/Auth.class.php +++ b/source/lib/SihnonFramework/Auth.class.php @@ -80,16 +80,28 @@ class SihnonFramework_Auth { */ public function addUser($username, $password) { + if ( ! is_subclass_of($this->backend, 'SihnonFramework_Auth_IUpdateable')) { + throw new SihnonFramework_Exception_NotImplemented(); + } + return $this->backend->addUser($username, $password); } public function removeUser() { + if ( ! is_subclass_of($this->backend, 'SihnonFramework_Auth_IUpdateable')) { + throw new SihnonFramework_Exception_NotImplemented(); + } + $this->backend->removeUser($this->user); $this->user = null; $this->authenticated = false; } public function changePassword($new_password) { + if ( ! is_subclass_of($this->backend, 'SihnonFramework_Auth_IUpdateable')) { + throw new SihnonFramework_Exception_NotImplemented(); + } + $this->backend->changePassword($this->user, $new_password); } @@ -102,6 +114,10 @@ class SihnonFramework_Auth { return false; } + if ( ! is_subclass_of($this->backend, 'SihnonFramework_Auth_IPermissionable')) { + throw new SihnonFramework_Exception_NotImplemented(); + } + return $this->backend->isAdministrator($this->user); } @@ -114,6 +130,10 @@ class SihnonFramework_Auth { return false; } + if ( ! is_subclass_of($this->backend, 'SihnonFramework_Auth_IFinelyPermissionable')) { + throw new SihnonFramework_Exception_NotImplemented(); + } + return $this->backend->hasPermission($this->user, $permission); } diff --git a/source/lib/SihnonFramework/Auth/Plugin/Config.class.php b/source/lib/SihnonFramework/Auth/Plugin/Config.class.php new file mode 100644 index 0000000..5bc46e6 --- /dev/null +++ b/source/lib/SihnonFramework/Auth/Plugin/Config.class.php @@ -0,0 +1,79 @@ +config = $config; + + Sihnon_Auth_Plugin_Config_User::init($config); + } + + /* + * IPlugin methods + */ + + public static function create(SihnonFramework_Config $config) { + return new self($config); + } + + public function userExists($username) { + return Sihnon_Auth_Plugin_Config_User::exists($username); + } + + public function listUsers() { + return array( + Sihnon_Auth_Plugin_Config_User::loadAdmin(), + ); + } + + public function authenticate($username, $password) { + $user = Sihnon_Auth_Plugin_Config_User::load($username); + + if ( ! $user->checkPassword($password)) { + throw new Sihnon_Exception_IncorrectPassword(); + } + + return $user; + } + + public function authenticateSession($username) { + return Sihnon_Auth_Plugin_Config_User::load($username); + } + + /* + * IUpdateable methods + */ + + public function addUser($username, $password) { + throw new Sihnon_Exception_NotImplemented(); + } + + public function removeUser(Sihnon_Auth_IUser $user) { + throw new Sihnon_Exception_NotImplemented(); + } + + public function changePassword(Sihnon_Auth_IUser $user, $new_password) { + $user->changePassword($new_password); + } + + /* + * IPermissionable methods + */ + + public function isAdministrator(Sihnon_Auth_IUser $user) { + return $user->isAdministrator(); + } + + public function hasPermission(Sihnon_Auth_IUser $user, $permission) { + return $user->isAdministrator(); + } + +} + +?> \ No newline at end of file diff --git a/source/lib/SihnonFramework/Auth/Plugin/Config/User.class.php b/source/lib/SihnonFramework/Auth/Plugin/Config/User.class.php new file mode 100644 index 0000000..facd67a --- /dev/null +++ b/source/lib/SihnonFramework/Auth/Plugin/Config/User.class.php @@ -0,0 +1,76 @@ +username = static::$config->get('auth.Config.admin-username'); + $user->password = static::$config->get('auth.Config.admin-password'); + + return $user; + } + + public static function loadAdmin() { + return static::load(static::administratorUsername()); + } + + public function username() { + return $this->username; + } + + public function checkPassword($password) { + return ($this->password == sha1($password)); + } + + public function changePassword($new_password) { + $this->password = sha1($new_password); + static::$config->set('auth.Config.admin-password', $this->password); + } + + public function isAdministrator() { + return true; + } + + protected static function administratorUsername() { + if ( ! static::$config->exists('auth.Config.admin-username')) { + return 'admin'; + } + + return static::$config->get('auth.Config.admin-username'); + } + + public function __get($name) { + switch ($name) { + case 'username': { + return $this->username; + } break; + + default: { + throw new Sihnon_Exception_InvalidProperty($name); + } break; + } + } + + public function __set($name, $value) { + throw new Sihnon_Exception_NotImplemented(); + } +} + +?> \ No newline at end of file diff --git a/source/lib/SihnonFramework/Auth/Plugin/FlatFile.class.php b/source/lib/SihnonFramework/Auth/Plugin/FlatFile.class.php index 7480d9e..de8f856 100644 --- a/source/lib/SihnonFramework/Auth/Plugin/FlatFile.class.php +++ b/source/lib/SihnonFramework/Auth/Plugin/FlatFile.class.php @@ -3,8 +3,8 @@ class SihnonFramework_Auth_Plugin_FlatFile extends Sihnon_PluginBase implements Sihnon_Auth_IPlugin, - Sihnon_Auth_IUpdateable, - Sihnon_Auth_IPermissionable { + Sihnon_Auth_IUpdateable, + Sihnon_Auth_IPermissionable { protected $config; @@ -47,7 +47,7 @@ class SihnonFramework_Auth_Plugin_FlatFile */ public function addUser($username, $password) { - return Sihnon_Auth_Plugin_Database_User::add($username, $password); + return Sihnon_Auth_Plugin_FlatFile_User::add($username, $password); } public function removeUser(Sihnon_Auth_IUser $user) { diff --git a/source/lib/SihnonFramework/Auth/Plugin/LDAP.class.php b/source/lib/SihnonFramework/Auth/Plugin/LDAP.class.php new file mode 100644 index 0000000..edab9da --- /dev/null +++ b/source/lib/SihnonFramework/Auth/Plugin/LDAP.class.php @@ -0,0 +1,116 @@ +config = $config; + + $this->initInstance(); + } + + protected function initInstance() { + $this->ldap = ldap_connect($this->config->get('auth.LDAP.servers'), $this->config->get('auth.LDAP.port', 389)); + if ( ! $this->ldap) { + throw new SihnonFramework_Exception_LDAPConnectionFailed(); + } + + if ($this->config->get('auth.LDAP.start-tls', false)) { + if ( ! ldap_start_tls($this->ldap)) { + throw new Sihnon_Exception_LDAPSecureConnectionFailed(); + } + } + + $search_dn = $this->config->get('auth.LDAP.search-dn', null); + $search_password = $this->config->get('auth.LDAP.search-password', null); + if ( ! ldap_bind($this->ldap, $search_dn, $search_password)) { + var_dump("Failed to bind as", $search_dn, $search_password); + throw new SihnonFramework_Exception_LDAPBindFailed(); + } + + Sihnon_Auth_Plugin_LDAP_User::init( + $this->ldap, + $this->config->get('auth.LDAP.user-base-dn'), + $this->config->get('auth.LDAP.group-base-dn'), + $this->config->get('auth.LDAP.recursive-search', false) + ); + } + + /* + * IPlugin methods + */ + + public static function create(Sihnon_Config $config) { + return new self($config); + } + + public function userExists($username) { + return Sihnon_Auth_Plugin_LDAP_User::exists($username); + } + + public function listUsers() { + return Sihnon_Auth_Plugin_LDAP_User::all(); + } + + public function authenticate($username, $password) { + $user = Sihnon_Auth_Plugin_LDAP_User::load($username); + + if ( ! $user->checkPassword($password)) { + throw new Sihnon_Exception_IncorrectPassword(); + } + + return $user; + } + + public function authenticateSession($username) { + return Sihnon_Auth_Plugin_LDAP_User::load($username); + } + + /* + * IPermissionable methods + */ + + public function isAdministrator(Sihnon_Auth_IUser $user) { + return $user->isAdministrator(); + } + + public function hasPermission(Sihnon_Auth_IUser $user, $permission) { + return $user->hasPermission($permission); + } + public static function ldapEscape($input_str, $for_dn = false) { + // Taken from Douglas Davis at http://php.sihnon.net/manual/en/function.ldap-search.php#90158 + // see: + // RFC2254 + // http://msdn.microsoft.com/en-us/library/ms675768(VS.85).aspx + // http://www-03.ibm.com/systems/i/software/ldap/underdn.html + + $str = $input_str; + if ( ! is_array($str)) { + $str = array($str); + } + + if ($for_dn) { + $metaChars = array(',', '=', '+', '<', '>', ';', '\\', '"', '#'); + } + else { + $metaChars = array('*', '(', ')', '\\', chr(0)); + } + + $quotedMetaChars = array(); + foreach ($metaChars as $key => $value) { + $quotedMetaChars[$key] = '\\'.str_pad(dechex(ord($value)), 2, '0'); + } + $str = str_replace($metaChars,$quotedMetaChars,$str); //replace them + + return is_array($input_str) ? $str : $str[0]; + } + +} + +?> \ No newline at end of file diff --git a/source/lib/SihnonFramework/Auth/Plugin/LDAP/User.class.php b/source/lib/SihnonFramework/Auth/Plugin/LDAP/User.class.php new file mode 100644 index 0000000..370272f --- /dev/null +++ b/source/lib/SihnonFramework/Auth/Plugin/LDAP/User.class.php @@ -0,0 +1,139 @@ +fullname = $result['cn'][0]; + $user->username = $result['uid'][0]; + + return $user; + } + + public static function load($username) { + $ldap_username = Sihnon_Auth_Plugin_LDAP::ldapEscape($username); + $filter = "(&(objectClass=posixAccount)(uid={$ldap_username}))"; + + $search = ldap_search(static::$ldap, static::$user_base_dn, $filter, array('cn', 'uid'), 0, 1); + $result = ldap_get_entries(static::$ldap, $search); + + if ($result['count'] != 1) { + throw new Sihnon_Exception_UnknownUser($username); + } + + return static::fromLDAP($result[0]); + } + + public static function all() { + $filter = "(objectClass=posixAccount)"; + + $search = null; + if (static::$recursive_search) { + $search = ldap_search(static::$ldap, static::$user_base_dn, $filter, array('cn', 'uid'), 0); + } else { + $search = ldap_list(static::$ldap, static::$user_base_dn, $filter, array('cn', 'uid'), 0); + } + $result = ldap_get_entries(static::$ldap, $search); + + $users = array(); + for ($i = 0, $l = $result['count']; $i < $l; ++$i) { + $users[] = static::fromLDAP($result[$i]); + } + + return $users; + } + + public function username() { + return $this->username; + } + + public function checkPassword($password) { + $ldap_user_dn = Sihnon_Auth_Plugin_LDAP::ldapEscape($this->fullname, true); + return ldap_bind(static::$ldap, "cn={$ldap_user_dn},".static::$base_dn, $password); + } + + public function changePassword($new_password) { + throw new Sihnon_Exception_NotImplemented(); + } + + public function isAdministrator() { + return $this->hasPermission('wheel'); + } + + public function hasPermission($permission) { + return in_array($permission, $this->permissions()); + } + + public function permissions() { + if ($this->groups === null) { + $ldap_username = Sihnon_Auth_Plugin_LDAP::ldapEscape($this->username); + + $filter = "(&(objectClass=posixGroup)(memberUid={$ldap_username}))"; + + $search = null; + if (static::$recursive_search) { + $search = ldap_search(static::$ldap, static::$group_base_dn, $filter, array('cn'), 0); + } else { + $search = ldap_list(static::$ldap, static::$group_base_dn, $filter, array('cn'), 0); + } + $result = ldap_get_entries(static::$ldap, $search); + + $this->groups = array(); + for ($i = 0, $l = $result['count']; $i < $l; ++$i) { + $this->groups[] = $result[$i]['cn'][0]; + } + + } + + return $this->groups; + } + + public function __get($name) { + switch ($name) { + case 'username': { + return $this->username; + } break; + + case 'fullname': { + return $this->fullname; + } break; + + default: { + throw new Sihnon_Exception_InvalidProperty($name); + } break; + } + } + + public function __set($name, $value) { + throw new Sihnon_Exception_NotImplemented(); + } +} + +?> \ No newline at end of file diff --git a/source/lib/SihnonFramework/Auth/Plugin/None.class.php b/source/lib/SihnonFramework/Auth/Plugin/None.class.php new file mode 100644 index 0000000..ca02176 --- /dev/null +++ b/source/lib/SihnonFramework/Auth/Plugin/None.class.php @@ -0,0 +1,37 @@ + \ No newline at end of file diff --git a/source/lib/SihnonFramework/CSRF.php b/source/lib/SihnonFramework/CSRF.php new file mode 100644 index 0000000..30fd024 --- /dev/null +++ b/source/lib/SihnonFramework/CSRF.php @@ -0,0 +1,47 @@ +session = $main->session(); + + $this->prepareSession(); + } + + public function prepareSession() { + if ( ! $this->session->exists('csrf')) { + $this->session->set('csrf', uniqid(), true); + } + } + + public function generate() { + $key = uniqid(); + $check = $this->generateCheck($key); + + return "{$key}:{$check}"; + } + + protected function generateCheck($key) { + return sha1($key . $this->session->get('csrf')); + } + + public function validate($token) { + list($key, $check) = explode(':', $token); + if ($check != $this->generateCheck($key)) { + throw new SihnonFramework_Exception_CSRFVerificationFailure(); + } + + return true; + } + + public function validatePost() { + $token = SihnonFramework_Main::issetelse($_POST['csrftoken'], 'SihnonFramework_Exception_CSRFVerificationFailure'); + return $this->validate($token); + } + +} + +?> \ No newline at end of file diff --git a/source/lib/SihnonFramework/Config.class.php b/source/lib/SihnonFramework/Config.class.php index 1f804ab..5dde872 100644 --- a/source/lib/SihnonFramework/Config.class.php +++ b/source/lib/SihnonFramework/Config.class.php @@ -109,9 +109,13 @@ class SihnonFramework_Config { * * @param string $key Name of the setting */ - public function get($key) { - if (!isset($this->settings[$key])) { - throw new Sihnon_Exception_UnknownSetting($key); + public function get($key, $default = 'SihnonFramework_Exception_UnknownSetting') { + if ( ! isset($this->settings[$key])) { + if (is_string($default) && preg_match('/^Sihnon(Framework)?_Exception/', $default) && class_exists($default) && is_subclass_of($default, 'SihnonFramework_Exception')) { + throw new $default(); + } + + return $default; } return static::unpack($this->settings[$key]['type'], $this->settings[$key]['value']); diff --git a/source/lib/SihnonFramework/Exceptions.class.php b/source/lib/SihnonFramework/Exceptions.class.php index c67f4d3..5834981 100644 --- a/source/lib/SihnonFramework/Exceptions.class.php +++ b/source/lib/SihnonFramework/Exceptions.class.php @@ -52,4 +52,12 @@ class SihnonFramework_Exception_DaemonException extends SihnonFramework_E class SihnonFramework_Exception_AlreadyRunning extends SihnonFramework_Exception_DaemonException {}; class SihnonFramework_Exception_LockingFailed extends SihnonFramework_Exception_DaemonException {}; +class SihnonFramework_Exception_LDAPException extends SihnonFramework_Exception {}; +class SihnonFramework_Exception_LDAPConnectionFailed extends SihnonFramework_Exception_LDAPException {}; +class SihnonFramework_Exception_LDAPSecureConnectionFailed extends SihnonFramework_Exception_LDAPException {}; +class SihnonFramework_Exception_LDAPBindFailed extends SihnonFramework_Exception_LDAPException {}; + +class SihnonFramework_Exception_CSRFException extends SihnonFramework_Exception {}; +class SihnonFramework_Exception_CSRFVerificationFailure extends SihnonFramework_Exception_CSRFException {}; + ?> diff --git a/source/lib/SihnonFramework/Log/Plugin/FlatFile.class.php b/source/lib/SihnonFramework/Log/Plugin/FlatFile.class.php index 399bf89..1c8dc33 100644 --- a/source/lib/SihnonFramework/Log/Plugin/FlatFile.class.php +++ b/source/lib/SihnonFramework/Log/Plugin/FlatFile.class.php @@ -67,6 +67,7 @@ class SihnonFramework_Log_Plugin_FlatFile extends SihnonFramework_Log_PluginBase // Make some alterations for ease of display $fields_map['timestamp'] = date('d/m/y H:i:s', $fields_map['ctime']); + $fields_map['shortfile'] = basename($fields_map['file']); // split the map back out again now the modifications have been made $fields = array_keys($fields_map); diff --git a/source/lib/SihnonFramework/Main.class.php b/source/lib/SihnonFramework/Main.class.php index 1a17a0f..8c00c7f 100644 --- a/source/lib/SihnonFramework/Main.class.php +++ b/source/lib/SihnonFramework/Main.class.php @@ -129,6 +129,23 @@ class SihnonFramework_Main { } public static function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) { + + $ignore_patterns = array( + /* Really don't consider this to be a bug, and make extensive use of this in the plugin architecture */ + '/^Declaration of .* should be compatible with that of .*/', + + /* This error is already handled in Database, but the warning is still triggered by PHP */ + '/^PDO::__construct.*: MySQL server has gone away/', + ); + + foreach ($ignore_patterns as $pattern) { + if (preg_match($pattern, $errstr)) { + if (defined('Sihnon_MaskKnownErrors') && Sihnon_MaskKnownErrors) { + return false; + } + } + } + $severity = ''; switch ($errno) { case E_NOTICE: @@ -199,6 +216,10 @@ class SihnonFramework_Main { $exceptions_filename = /*$class['subclass_dir_prefix'] .*/ preg_replace('/_/', '/', $matches[1]) . 'Exceptions.class.php'; if (stream_resolve_include_path($exceptions_filename)) { require_once($exceptions_filename); + // If that found the class, break here, otherwise look upstream + if (class_exists($classname, false)) { + return; + } } else { // Create this class to extend the Framework parent $parent_classname = preg_replace("/^{$class['subclass']}_/", "{$class['base']}_", $classname); @@ -343,6 +364,22 @@ class SihnonFramework_Main { return true; } + public static function recursiveFilesize($file) { + $size = 0; + if (is_dir($file)) { + $objects = scandir($file); + foreach ($objects as $object) { + if ($object != "." && $object != "..") { + $size += static::recursiveFilesize($file . '/' . $object); + } + } + } else { + $size += filesize($file); + } + + return $size; + } + public static function issetelse($var, $default = null) { if (isset($var)) { return $var; @@ -434,7 +471,7 @@ class SihnonFramework_Main { $size = $bytes; $ptr = count($labels) - 1; - while ($ptr >= 0 && $bytes < $limits[$ptr]) { + while ($ptr > 0 && $bytes < $limits[$ptr]) { --$ptr; } diff --git a/source/lib/SihnonFramework/Session.class.php b/source/lib/SihnonFramework/Session.class.php index b6b3ccb..e5fc315 100644 --- a/source/lib/SihnonFramework/Session.class.php +++ b/source/lib/SihnonFramework/Session.class.php @@ -7,11 +7,13 @@ class SihnonFramework_Session { protected $enabled; protected $state; protected $dirty; + protected $sensitive; public function __construct(Sihnon_Config $config) { $this->config = $config; $this->enabled = false; $this->dirty = false; + $this->sensitive = array(); if ($this->config->exists('sessions') && $this->config->get('sessions')) { $this->enabled = true; @@ -47,8 +49,12 @@ class SihnonFramework_Session { } } - public function set($name, $value) { + public function set($name, $value, $sensitive = false) { $this->state[$name] = $value; + if ($sensitive) { + $this->sensitive[$name] = true; + } + $this->dirty = true; } @@ -73,6 +79,14 @@ class SihnonFramework_Session { if ($this->enabled) { session_regenerate_id(true); } + + // Clear any sensitive values + foreach ($this->sensitive as $name => $value) { + if ($value) { + $this->delete($name); + } + } + $this->sensitive = array(); } }