diff options
Diffstat (limited to '')
-rw-r--r-- | library/Icinga/User.php | 649 | ||||
-rw-r--r-- | library/Icinga/User/Preferences.php | 169 | ||||
-rw-r--r-- | library/Icinga/User/Preferences/PreferencesStore.php | 344 |
3 files changed, 1162 insertions, 0 deletions
diff --git a/library/Icinga/User.php b/library/Icinga/User.php new file mode 100644 index 0000000..8610dd0 --- /dev/null +++ b/library/Icinga/User.php @@ -0,0 +1,649 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga; + +use DateTimeZone; +use Icinga\Authentication\AdmissionLoader; +use InvalidArgumentException; +use Icinga\Application\Config; +use Icinga\Authentication\Role; +use Icinga\Exception\ProgrammingError; +use Icinga\User\Preferences; +use Icinga\Web\Navigation\Navigation; + +/** + * This class represents an authorized user + * + * You can retrieve authorization information (@TODO: Not implemented yet) or user information + */ +class User +{ + /** + * Firstname + * + * @var string + */ + protected $firstname; + + /** + * Lastname + * + * @var string + */ + protected $lastname; + + /** + * Users email address + * + * @var string + */ + protected $email; + + /** + * {@link username} without {@link domain} + * + * @var string + */ + protected $localUsername; + + /** + * Domain + * + * @var string + */ + protected $domain; + + /** + * More information about this user + * + * @var array + */ + protected $additionalInformation = array(); + + /** + * Information if the user is externally authenticated + * + * Keys: + * + * 0: origin username + * 1: origin field name + * + * @var array + */ + protected $externalUserInformation = array(); + + /** + * Whether restrictions should not apply to this user + * + * @var bool + */ + protected $unrestricted = false; + + /** + * Set of permissions + * + * @var array + */ + protected $permissions = array(); + + /** + * Set of restrictions + * + * @var array + */ + protected $restrictions = array(); + + /** + * Groups for this user + * + * @var array + */ + protected $groups = array(); + + /** + * Roles of this user + * + * @var Role[] + */ + protected $roles = array(); + + /** + * Preferences object + * + * @var Preferences + */ + protected $preferences; + + /** + * Whether the user is authenticated using a HTTP authentication mechanism + * + * @var bool + */ + protected $isHttpUser = false; + + /** + * Creates a user object given the provided information + * + * @param string $username + * @param string $firstname + * @param string $lastname + * @param string $email + */ + public function __construct($username, $firstname = null, $lastname = null, $email = null) + { + $this->setUsername($username); + + if ($firstname !== null) { + $this->setFirstname($firstname); + } + + if ($lastname !== null) { + $this->setLastname($lastname); + } + + if ($email !== null) { + $this->setEmail($email); + } + } + + /** + * Setter for preferences + * + * @param Preferences $preferences + * + * @return $this + */ + public function setPreferences(Preferences $preferences) + { + $this->preferences = $preferences; + return $this; + } + + /** + * Getter for preferences + * + * @return Preferences + */ + public function getPreferences() + { + if ($this->preferences === null) { + $this->preferences = new Preferences(); + } + + return $this->preferences; + } + + /** + * Return all groups this user belongs to + * + * @return array + */ + public function getGroups() + { + return $this->groups; + } + + /** + * Set the groups this user belongs to + * + * @param array $groups + * + * @return $this + */ + public function setGroups(array $groups) + { + $this->groups = $groups; + return $this; + } + + /** + * Return true if the user is a member of this group + * + * @param string $group + * + * @return boolean + */ + public function isMemberOf($group) + { + return in_array($group, $this->groups); + } + + /** + * Get whether restrictions should not apply to this user + * + * @return bool + */ + public function isUnrestricted() + { + return $this->unrestricted; + } + + /** + * Set whether restrictions should not apply to this user + * + * @param bool $state + * + * @return $this + */ + public function setIsUnrestricted($state) + { + $this->unrestricted = (bool) $state; + + return $this; + } + + /** + * Get the user's permissions + * + * @return array + */ + public function getPermissions() + { + return $this->permissions; + } + + /** + * Set the user's permissions + * + * @param array $permissions + * + * @return $this + */ + public function setPermissions(array $permissions) + { + if (! empty($permissions)) { + natcasesort($permissions); + $this->permissions = array_combine($permissions, $permissions); + } + return $this; + } + + /** + * Return restriction information for this user + * + * @param string $name + * + * @return array + */ + public function getRestrictions($name) + { + if (array_key_exists($name, $this->restrictions)) { + return $this->restrictions[$name]; + } + + return array(); + } + + /** + * Set the user's restrictions + * + * @param string[] $restrictions + * + * @return $this + */ + public function setRestrictions(array $restrictions) + { + $this->restrictions = $restrictions; + return $this; + } + + /** + * Get the roles of the user + * + * @return Role[] + */ + public function getRoles() + { + return $this->roles; + } + + /** + * Set the roles of the user + * + * @param Role[] $roles + * + * @return $this + */ + public function setRoles(array $roles) + { + $this->roles = $roles; + return $this; + } + + /** + * Getter for username + * + * @return string + */ + public function getUsername() + { + return $this->domain === null ? $this->localUsername : $this->localUsername . '@' . $this->domain; + } + + /** + * Setter for username + * + * @param string $name + * + * @return $this + */ + public function setUsername($name) + { + $parts = explode('\\', $name, 2); + if (count($parts) === 2) { + list($this->domain, $this->localUsername) = $parts; + } else { + $parts = explode('@', $name, 2); + if (count($parts) === 2) { + list($this->localUsername, $this->domain) = $parts; + } else { + $this->localUsername = $name; + $this->domain = null; + } + } + + return $this; + } + + /** + * Getter for firstname + * + * @return string + */ + public function getFirstname() + { + return $this->firstname; + } + + /** + * Setter for firstname + * + * @param string $name + * + * @return $this + */ + public function setFirstname($name) + { + $this->firstname = $name; + return $this; + } + + /** + * Getter for lastname + * + * @return string + */ + public function getLastname() + { + return $this->lastname; + } + + /** + * Setter for lastname + * + * @param string $name + * + * @return $this + */ + public function setLastname($name) + { + $this->lastname = $name; + return $this; + } + + /** + * Getter for email + * + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * Setter for mail + * + * @param string $mail + * + * @return $this + * + * @throws InvalidArgumentException When an invalid mail is provided + */ + public function setEmail($mail) + { + if ($mail !== null && !filter_var($mail, FILTER_VALIDATE_EMAIL)) { + throw new InvalidArgumentException( + sprintf('Invalid mail given for user %s: %s', $this->getUsername(), $mail) + ); + } + + $this->email = $mail; + return $this; + } + + /** + * Set the domain + * + * @param string $domain + * + * @return $this + */ + public function setDomain($domain) + { + if ($domain && ($domain = trim($domain))) { + $this->domain = $domain; + } + + return $this; + } + + /** + * Get whether the user has a domain + * + * @return bool + */ + public function hasDomain() + { + return $this->domain !== null; + } + + /** + * Get the domain + * + * @return string + * + * @throws ProgrammingError If the user does not have a domain + */ + public function getDomain() + { + if ($this->domain === null) { + throw new ProgrammingError( + 'User does not have a domain.' + . ' Use User::hasDomain() to check whether the user has a domain beforehand.' + ); + } + return $this->domain; + } + + /** + * Get the local username, ie. the username without its domain + * + * @return string + */ + public function getLocalUsername() + { + return $this->localUsername; + } + + /** + * Set additional information about user + * + * @param string $key + * @param string $value + * + * @return $this + */ + public function setAdditional($key, $value) + { + $this->additionalInformation[$key] = $value; + return $this; + } + + /** + * Getter for additional information + * + * @param string $key + * @return mixed|null + */ + public function getAdditional($key) + { + if (isset($this->additionalInformation[$key])) { + return $this->additionalInformation[$key]; + } + + return null; + } + + /** + * Retrieve the user's timezone + * + * If the user did not set a timezone, the default timezone set via config.ini is returned + * + * @return DateTimeZone + */ + public function getTimeZone() + { + $tz = $this->preferences->get('timezone'); + if ($tz === null) { + $tz = date_default_timezone_get(); + } + + return new DateTimeZone($tz); + } + + /** + * Set additional external user information + * + * @param string $username + * @param string $field + * + * @return $this + */ + public function setExternalUserInformation($username, $field) + { + $this->externalUserInformation = array($username, $field); + return $this; + } + + /** + * Get additional external user information + * + * @return array + */ + public function getExternalUserInformation() + { + return $this->externalUserInformation; + } + + /** + * Return true if user has external user information set + * + * @return bool + */ + public function isExternalUser() + { + return ! empty($this->externalUserInformation); + } + + /** + * Get whether the user is authenticated using a HTTP authentication mechanism + * + * @return bool + */ + public function getIsHttpUser() + { + return $this->isHttpUser; + } + + /** + * Set whether the user is authenticated using a HTTP authentication mechanism + * + * @param bool $isHttpUser + * + * @return $this + */ + public function setIsHttpUser($isHttpUser = true) + { + $this->isHttpUser = (bool) $isHttpUser; + return $this; + } + + /** + * Whether the user has a given permission + * + * @param string $requiredPermission + * + * @return bool + */ + public function can($requiredPermission) + { + list($permissions, $refusals) = AdmissionLoader::migrateLegacyPermissions([$requiredPermission]); + if (! empty($permissions)) { + $requiredPermission = array_pop($permissions); + } elseif (! empty($refusals)) { + throw new InvalidArgumentException( + 'Refusals are not supported anymore. Check for a grant instead!' + ); + } + + $granted = false; + foreach ($this->getRoles() as $role) { + if ($role->denies($requiredPermission)) { + return false; + } + + if (! $granted && $role->grants($requiredPermission)) { + $granted = true; + } + } + + return $granted; + } + + /** + * Load and return this user's configured navigation of the given type + * + * @param string $type + * + * @return Navigation + */ + public function getNavigation($type) + { + $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type, $this->getUsername()); + + if ($type === 'dashboard-pane') { + $panes = array(); + foreach ($config as $dashletName => $dashletConfig) { + // TODO: Throw ConfigurationError if pane or url is missing + $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url; + } + + $navigation = new Navigation(); + foreach ($panes as $paneName => $dashlets) { + $navigation->addItem( + $paneName, + array( + 'type' => 'dashboard-pane', + 'dashlets' => $dashlets + ) + ); + } + } else { + $navigation = Navigation::fromConfig($config); + } + + return $navigation; + } +} diff --git a/library/Icinga/User/Preferences.php b/library/Icinga/User/Preferences.php new file mode 100644 index 0000000..b09462b --- /dev/null +++ b/library/Icinga/User/Preferences.php @@ -0,0 +1,169 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\User; + +use Countable; + +/** + * User preferences container + * + * Usage example: + * <code> + * <?php + * + * use Icinga\User\Preferences; + * + * $preferences = new Preferences(); // Start with empty preferences + * + * $preferences = new Preferences(array('aPreference' => 'value')); // Start with initial preferences + * + * $preferences->aNewPreference = 'value'; // Set a preference + * + * unset($preferences->aPreference); // Unset a preference + * + * // Retrieve a preference and return a default value if the preference does not exist + * $anotherPreference = $preferences->get('anotherPreference', 'defaultValue'); + */ +class Preferences implements Countable +{ + /** + * Preferences key-value array + * + * @var array + */ + protected $preferences = array(); + + /** + * Constructor + * + * @param array $preferences Preferences key-value array + */ + public function __construct(array $preferences = array()) + { + $this->preferences = $preferences; + } + + /** + * Count all preferences + * + * @return int The number of preferences + */ + public function count(): int + { + return count($this->preferences); + } + + /** + * Determine whether a preference exists + * + * @param string $name + * + * @return bool + */ + public function has($name) + { + return array_key_exists($name, $this->preferences); + } + + /** + * Write data to a preference + * + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->preferences[$name] = $value; + } + + /** + * Retrieve a preference section + * + * @param string $name + * + * @return array|null + */ + public function get($name) + { + if (array_key_exists($name, $this->preferences)) { + return $this->preferences[$name]; + } + + return null; + } + + /** + * Retrieve a value from a specific section + * + * @param string $section + * @param string $name + * @param null $default + * + * @return array|null + */ + public function getValue($section, $name, $default = null) + { + if (array_key_exists($section, $this->preferences) + && array_key_exists($name, $this->preferences[$section]) + ) { + return $this->preferences[$section][$name]; + } + + return $default; + } + + /** + * Magic method so that $obj->value will work. + * + * @param string $name + * + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Remove a given preference + * + * @param string $name Preference name + */ + public function remove($name) + { + unset($this->preferences[$name]); + } + + /** + * Determine if a preference is set and is not NULL + * + * @param string $name Preference name + * + * @return bool + */ + public function __isset($name) + { + return isset($this->preferences[$name]); + } + + /** + * Unset a given preference + * + * @param string $name Preference name + */ + public function __unset($name) + { + $this->remove($name); + } + + /** + * Get preferences as array + * + * @return array + */ + public function toArray() + { + return $this->preferences; + } +} diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php new file mode 100644 index 0000000..8ecc677 --- /dev/null +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -0,0 +1,344 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\User\Preferences; + +use Exception; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; +use Icinga\User; +use Icinga\User\Preferences; +use Icinga\Data\ConfigObject; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\ConfigurationError; +use Zend_Db_Expr; + +/** + * Preferences store factory + * + * Load and save user preferences by using a database + * + * Usage example: + * <code> + * <?php + * + * use Icinga\Data\ConfigObject; + * use Icinga\User\Preferences; + * use Icinga\User\Preferences\PreferencesStore; + * + * // Create a db store + * $store = PreferencesStore::create( + * new ConfigObject( + * 'resource' => 'resource name' + * ), + * $user // Instance of \Icinga\User + * ); + * + * $preferences = new Preferences($store->load()); + * $preferences->aPreference = 'value'; + * $store->save($preferences); + * </code> + */ +class PreferencesStore +{ + /** + * Column name for username + */ + const COLUMN_USERNAME = 'username'; + + /** + * Column name for section + */ + const COLUMN_SECTION = 'section'; + + /** + * Column name for preference + */ + const COLUMN_PREFERENCE = 'name'; + + /** + * Column name for value + */ + const COLUMN_VALUE = 'value'; + + /** + * Column name for created time + */ + const COLUMN_CREATED_TIME = 'ctime'; + + /** + * Column name for modified time + */ + const COLUMN_MODIFIED_TIME = 'mtime'; + + /** + * Table name + * + * @var string + */ + protected $table = 'icingaweb_user_preference'; + + /** + * Stored preferences + * + * @var array + */ + protected $preferences = []; + + /** + * Store config + * + * @var ConfigObject + */ + protected $config; + + /** + * Given user + * + * @var User + */ + protected $user; + + /** + * Create a new store + * + * @param ConfigObject $config The config for this adapter + * @param User $user The user to which these preferences belong + */ + public function __construct(ConfigObject $config, User $user) + { + $this->config = $config; + $this->user = $user; + $this->init(); + } + + /** + * Getter for the store config + * + * @return ConfigObject + */ + public function getStoreConfig(): ConfigObject + { + return $this->config; + } + + /** + * Getter for the user + * + * @return User + */ + public function getUser(): User + { + return $this->user; + } + + /** + * Initialize the store + */ + protected function init(): void + { + } + + /** + * Load preferences from the database + * + * @return array + * + * @throws NotReadableError In case the database operation failed + */ + public function load(): array + { + try { + $select = $this->getStoreConfig()->connection->getDbAdapter()->select(); + $result = $select + ->from($this->table, [self::COLUMN_SECTION, self::COLUMN_PREFERENCE, self::COLUMN_VALUE]) + ->where(self::COLUMN_USERNAME . ' = ?', $this->getUser()->getUsername()) + ->query() + ->fetchAll(); + } catch (Exception $e) { + throw new NotReadableError( + 'Cannot fetch preferences for user %s from database', + $this->getUser()->getUsername(), + $e + ); + } + + if ($result !== false) { + $values = []; + foreach ($result as $row) { + $values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE}; + } + + $this->preferences = $values; + } + + return $this->preferences; + } + + /** + * Save the given preferences in the database + * + * @param Preferences $preferences The preferences to save + */ + public function save(Preferences $preferences): void + { + $preferences = $preferences->toArray(); + + $sections = array_keys($preferences); + + foreach ($sections as $section) { + if (! array_key_exists($section, $this->preferences)) { + $this->preferences[$section] = []; + } + + if (! array_key_exists($section, $preferences)) { + $preferences[$section] = []; + } + + $toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]); + if (!empty($toBeInserted)) { + $this->insert($toBeInserted, $section); + } + + $toBeUpdated = array_intersect_key( + array_diff_assoc($preferences[$section], $this->preferences[$section]), + array_diff_assoc($this->preferences[$section], $preferences[$section]) + ); + + if (!empty($toBeUpdated)) { + $this->update($toBeUpdated, $section); + } + + $toBeDeleted = array_keys(array_diff_key($this->preferences[$section], $preferences[$section])); + if (!empty($toBeDeleted)) { + $this->delete($toBeDeleted, $section); + } + } + } + + /** + * Insert the given preferences into the database + * + * @param array $preferences The preferences to insert + * @param string $section The preferences in section to update + * + * @throws NotWritableError In case the database operation failed + */ + protected function insert(array $preferences, string $section): void + { + /** @var \Zend_Db_Adapter_Abstract $db */ + $db = $this->getStoreConfig()->connection->getDbAdapter(); + + try { + foreach ($preferences as $key => $value) { + $db->insert( + $this->table, + [ + self::COLUMN_USERNAME => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_SECTION) => $section, + $db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key, + self::COLUMN_VALUE => $value, + self::COLUMN_CREATED_TIME => new Zend_Db_Expr('NOW()'), + self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') + ] + ); + } + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot insert preferences for user %s into database', + $this->getUser()->getUsername(), + $e + ); + } + } + + /** + * Update the given preferences in the database + * + * @param array $preferences The preferences to update + * @param string $section The preferences in section to update + * + * @throws NotWritableError In case the database operation failed + */ + protected function update(array $preferences, string $section): void + { + /** @var \Zend_Db_Adapter_Abstract $db */ + $db = $this->getStoreConfig()->connection->getDbAdapter(); + + try { + foreach ($preferences as $key => $value) { + $db->update( + $this->table, + [ + self::COLUMN_VALUE => $value, + self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') + ], + [ + self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, + $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key + ] + ); + } + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot update preferences for user %s in database', + $this->getUser()->getUsername(), + $e + ); + } + } + + /** + * Delete the given preference names from the database + * + * @param array $preferenceKeys The preference names to delete + * @param string $section The preferences in section to update + * + * @throws NotWritableError In case the database operation failed + */ + protected function delete(array $preferenceKeys, string $section): void + { + /** @var \Zend_Db_Adapter_Abstract $db */ + $db = $this->getStoreConfig()->connection->getDbAdapter(); + + try { + $db->delete( + $this->table, + [ + self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, + $db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys + ] + ); + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot delete preferences for user %s from database', + $this->getUser()->getUsername(), + $e + ); + } + } + + /** + * Create preferences storage adapter from config + * + * @param ConfigObject $config The config for the adapter + * @param User $user The user to which these preferences belong + * + * @return self + * + * @throws ConfigurationError When the configuration defines an invalid storage type + */ + public static function create(ConfigObject $config, User $user): self + { + $resourceConfig = ResourceFactory::getResourceConfig($config->resource); + if ($resourceConfig->db === 'mysql') { + $resourceConfig->charset = 'utf8mb4'; + } + + $config->connection = ResourceFactory::createResource($resourceConfig); + + return new self($config, $user); + } +} |