summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Authentication/Auth.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Authentication/Auth.php')
-rw-r--r--library/Icinga/Authentication/Auth.php453
1 files changed, 453 insertions, 0 deletions
diff --git a/library/Icinga/Authentication/Auth.php b/library/Icinga/Authentication/Auth.php
new file mode 100644
index 0000000..f358eac
--- /dev/null
+++ b/library/Icinga/Authentication/Auth.php
@@ -0,0 +1,453 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Application\Hook\AuditHook;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Authentication\User\ExternalBackend;
+use Icinga\Authentication\UserGroup\UserGroupBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotReadableError;
+use Icinga\User;
+use Icinga\User\Preferences;
+use Icinga\User\Preferences\PreferencesStore;
+use Icinga\Web\Session;
+use Icinga\Web\StyleSheet;
+
+class Auth
+{
+ /**
+ * Singleton instance
+ *
+ * @var self
+ */
+ private static $instance;
+
+ /**
+ * Request
+ *
+ * @var \Icinga\Web\Request
+ */
+ protected $request;
+
+ /**
+ * Response
+ *
+ * @var \Icinga\Web\Response
+ */
+ protected $response;
+
+ /**
+ * Authenticated user
+ *
+ * @var User|null
+ */
+ private $user;
+
+
+ /**
+ * @see getInstance()
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Get the authentication manager
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Get the auth chain
+ *
+ * @return AuthChain
+ */
+ public function getAuthChain()
+ {
+ return new AuthChain();
+ }
+
+ /**
+ * Get whether the user is authenticated
+ *
+ * @return bool
+ */
+ public function isAuthenticated()
+ {
+ if ($this->user !== null) {
+ return true;
+ }
+ $this->authenticateFromSession();
+ if ($this->user === null && ! $this->authExternal()) {
+ return false;
+ }
+ return true;
+ }
+
+ public function setAuthenticated(User $user, $persist = true)
+ {
+ $this->setupUser($user);
+
+ // Reload CSS if the theme changed
+ $themingConfig = Icinga::app()->getConfig()->getSection('themes');
+ $userTheme = $user->getPreferences()->getValue('icingaweb', 'theme');
+ if (! (bool) $themingConfig->get('disabled', false) && $userTheme !== null) {
+ $defaultTheme = $themingConfig->get('default', StyleSheet::DEFAULT_THEME);
+ if ($userTheme !== $defaultTheme) {
+ $this->getResponse()->setReloadCss(true);
+ }
+ }
+
+ // Also reload CSS if the theme mode changed
+ $themeMode = $user->getPreferences()->getValue('icingaweb', 'theme_mode');
+ if ($themeMode && $themeMode !== StyleSheet::DEFAULT_MODE) {
+ $this->getResponse()->setReloadCss(true);
+ }
+
+ // Reload entire layout if the locale changed
+ if (($locale = $user->getPreferences()->getValue('icingaweb', 'language')) !== null) {
+ if (setlocale(LC_ALL, 0) !== $locale && $this->getRequest()->isXmlHttpRequest()) {
+ $this->getResponse()->setHeader('X-Icinga-Redirect-Http', 'yes');
+ }
+ }
+
+ $this->user = $user;
+ if ($persist) {
+ $this->persistCurrentUser();
+ }
+
+ AuditHook::logActivity('login', 'User logged in');
+ }
+
+ /**
+ * Getter for groups belonged to authenticated user
+ *
+ * @return array
+ * @see User::getGroups
+ */
+ public function getGroups()
+ {
+ return $this->user->getGroups();
+ }
+
+ /**
+ * Get the request
+ *
+ * @return \Icinga\Web\Request
+ */
+ public function getRequest()
+ {
+ if ($this->request === null) {
+ $this->request = Icinga::app()->getRequest();
+ }
+ return $this->request;
+ }
+
+ /**
+ * Get the response
+ *
+ * @return \Icinga\Web\Response
+ */
+ public function getResponse()
+ {
+ if ($this->response === null) {
+ $this->response = Icinga::app()->getResponse();
+ }
+ return $this->response;
+ }
+
+ /**
+ * Get applied restrictions matching a given restriction name
+ *
+ * Returns a list of applied restrictions, empty if no user is
+ * authenticated
+ *
+ * @param string $restriction Restriction name
+ * @return array
+ */
+ public function getRestrictions($restriction)
+ {
+ if (! $this->isAuthenticated()) {
+ return array();
+ }
+ return $this->user->getRestrictions($restriction);
+ }
+
+ /**
+ * Returns the current user or null if no user is authenticated
+ *
+ * @return User|null
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Set the authenticated user
+ *
+ * Note that this method just sets the authenticated user and thus bypasses our default authentication process in
+ * {@link setAuthenticated()}.
+ *
+ * @param User $user
+ *
+ * @return $this
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+
+ return $this;
+ }
+
+ /**
+ * Try to authenticate the user with the current session
+ *
+ * Authentication for externally-authenticated users will be revoked if the username changed or external
+ * authentication is no longer in effect
+ */
+ public function authenticateFromSession()
+ {
+ $this->user = Session::getSession()->get('user');
+ if ($this->user !== null && $this->user->isExternalUser()) {
+ list($originUsername, $field) = $this->user->getExternalUserInformation();
+ $username = ExternalBackend::getRemoteUser($field);
+ if ($username === null || $username !== $originUsername) {
+ $this->removeAuthorization();
+ }
+ }
+ }
+
+ /**
+ * Attempt to authenticate a user from external user backends
+ *
+ * @return bool
+ */
+ protected function authExternal()
+ {
+ $user = new User('');
+ foreach ($this->getAuthChain() as $userBackend) {
+ if ($userBackend instanceof ExternalBackend) {
+ if ($userBackend->authenticate($user)) {
+ if (! $user->hasDomain()) {
+ $user->setDomain(Config::app()->get('authentication', 'default_domain'));
+ }
+ $this->setAuthenticated($user);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Attempt to authenticate a user using HTTP authentication on API requests only
+ *
+ * Supports only the Basic HTTP authentication scheme. XHR will be ignored.
+ *
+ * @return bool
+ */
+ public function authHttp()
+ {
+ $request = $this->getRequest();
+ $header = $request->getHeader('Authorization');
+ if (empty($header)) {
+ return false;
+ }
+ list($scheme) = explode(' ', $header, 2);
+ if ($scheme !== 'Basic') {
+ return false;
+ }
+ $authorization = substr($header, strlen('Basic '));
+ $credentials = base64_decode($authorization);
+ $credentials = array_filter(explode(':', $credentials, 2));
+ if (count($credentials) !== 2) {
+ // Deny empty username and/or password
+ return false;
+ }
+ $user = new User($credentials[0]);
+ if (! $user->hasDomain()) {
+ $user->setDomain(Config::app()->get('authentication', 'default_domain'));
+ }
+ $password = $credentials[1];
+ if ($this->getAuthChain()->setSkipExternalBackends(true)->authenticate($user, $password)) {
+ $this->setAuthenticated($user, false);
+ $user->setIsHttpUser(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Challenge client immediately for HTTP authentication
+ *
+ * Sends the response w/ the 401 Unauthorized status code and WWW-Authenticate header.
+ */
+ public function challengeHttp()
+ {
+ $response = $this->getResponse();
+ $response->setHttpResponseCode(401);
+ $response->setHeader('WWW-Authenticate', 'Basic realm="Icinga Web 2"');
+ $response->sendHeaders();
+ exit();
+ }
+
+ /**
+ * Whether an authenticated user has a given permission
+ *
+ * @param string $permission Permission name
+ *
+ * @return bool True if the user owns the given permission, false if not or if not authenticated
+ */
+ public function hasPermission($permission)
+ {
+ if (! $this->isAuthenticated()) {
+ return false;
+ }
+ return $this->user->can($permission);
+ }
+
+ /**
+ * Writes the current user to the session
+ */
+ public function persistCurrentUser()
+ {
+ // @TODO(el): https://dev.icinga.com/issues/10646
+ $params = session_get_cookie_params();
+ setcookie(
+ 'icingaweb2-session',
+ time(),
+ 0,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
+ Session::getSession()->set('user', $this->user)->refreshId();
+ }
+
+ /**
+ * Purges the current authorization information and session
+ */
+ public function removeAuthorization()
+ {
+ AuditHook::logActivity('logout', 'User logged out');
+ $this->user = null;
+ Session::getSession()->purge();
+ }
+
+ /**
+ * Setup the given user
+ *
+ * This loads preferences, groups and roles.
+ *
+ * @param User $user
+ *
+ * @return void
+ */
+ public function setupUser(User $user)
+ {
+ // Load the user's preferences
+
+ try {
+ $config = Config::app();
+ } catch (NotReadableError $e) {
+ Logger::error(
+ new IcingaException(
+ 'Cannot load preferences for user "%s". An exception was thrown: %s',
+ $user->getUsername(),
+ $e
+ )
+ );
+ $config = new Config();
+ }
+
+ $preferencesConfig = new ConfigObject([
+ 'resource' => $config->get('global', 'config_resource')
+ ]);
+
+ try {
+ $preferencesStore = PreferencesStore::create($preferencesConfig, $user);
+ $preferences = new Preferences($preferencesStore->load());
+ } catch (Exception $e) {
+ Logger::error(
+ new IcingaException(
+ 'Cannot load preferences for user "%s". An exception was thrown: %s',
+ $user->getUsername(),
+ $e
+ )
+ );
+ $preferences = new Preferences();
+ }
+
+ $user->setPreferences($preferences);
+
+ // Load the user's groups
+ $groups = $user->getGroups();
+ $userBackendName = $user->getAdditional('backend_name');
+ foreach (Config::app('groups') as $name => $config) {
+ $groupsUserBackend = $config->user_backend;
+ if ($groupsUserBackend
+ && $groupsUserBackend !== 'none'
+ && $userBackendName !== null
+ && $groupsUserBackend !== $userBackendName
+ ) {
+ // Do not ask for Group membership if a specific User Backend
+ // has been assigned to that Group Backend, and the user has
+ // been authenticated by another User Backend
+ continue;
+ }
+
+ try {
+ $groupBackend = UserGroupBackend::create($name, $config);
+ $groupsFromBackend = $groupBackend->getMemberships($user);
+ } catch (Exception $e) {
+ Logger::error(
+ 'Can\'t get group memberships for user \'%s\' from backend \'%s\'. An exception was thrown: %s',
+ $user->getUsername(),
+ $name,
+ $e
+ );
+ continue;
+ }
+
+ if (empty($groupsFromBackend)) {
+ Logger::debug(
+ 'No groups found in backend "%s" which the user "%s" is a member of.',
+ $name,
+ $user->getUsername()
+ );
+ continue;
+ }
+
+ $groupsFromBackend = array_values($groupsFromBackend);
+ Logger::debug(
+ 'Groups found in backend "%s" for user "%s": %s',
+ $name,
+ $user->getUsername(),
+ join(', ', $groupsFromBackend)
+ );
+ $groups = array_merge($groups, array_combine($groupsFromBackend, $groupsFromBackend));
+ }
+
+ $user->setGroups($groups);
+
+ // Load the user's roles
+ $admissionLoader = new AdmissionLoader();
+ $admissionLoader->applyRoles($user);
+ }
+}