diff --git a/docs/admin-manual/custom-authentication.md b/docs/admin-manual/custom-authentication.md index cadd9c6f8..a09f6b389 100644 --- a/docs/admin-manual/custom-authentication.md +++ b/docs/admin-manual/custom-authentication.md @@ -113,3 +113,68 @@ general: ``` You should now be able to use your FreeIPA credentials to log in to LibreTime. + +## Setup Header Authentication + +If you have an SSO system that supports trusted SSO header authentication such as [Authelia](https://www.authelia.com/), +you can configure LibreTime to login users based on those trusted headers. + +This allows users to only need to log in once on the SSO system and not need to log in again. It also allows LibreTime +to indirectly support other authentication mechanisms such as OAuth2. + +This ONLY affects Legacy/Legacy API auth and does NOT affect API V2 auth. + +### Configure Headers + +LibreTime needs to know what headers are sent, and what information is available to it. You can also +setup a predefined group mapping so users are automatically granted the desired permissions. + +This configuration is in `/etc/libretime/config.yml`. The following is an example configuration for an SSO service +that does the following: + +- Sends the username in the `Remote-User` HTTP header. +- Sends the email in the `Remote-Email` HTTP header. +- Sends the name in the `Remote-Name` HTTP header. Example `John Doe` +- Sends the comma delimited groups in the `Remote-Groups` HTTP header. Example `group 1,lt-admin,group2` +- Has an IP of `10.0.0.34` (not required). When not provided it is not checked. +- Users with the `lt-host` group should get host privileges. +- Users with the `lt-admin` group should get admin privileges. +- Users with the `lt-pm` group should get program manager privileges. +- Users with the `lt-superadmin` group should get super admin privileges. +- All other users should get guest privileges. + +```yml +header_auth: + user_header: Remote-User # This is the default and could be omitted + groups_header: Remote-Groups # This is the default and could be omitted + email_header: Remote-Email # This is the default and could be omitted + name_header: Remote-Name # This is the default and could be omitted + proxy_ip: 10.0.0.34 + group_map: + host: lt-host + program_manager: lt-pm + admin: lt-admin + superadmin: lt-superadmin +``` + +If the `user_header` is not found in the request, users will be kicked to the login page +with a message that their username/password is invalid and will not be able to log in. When `proxy_ip` is provided +it will check that the request is coming from the correct proxy before doing the login. This prevents users who have +internal network access from being able to login as whoever they want in LibreTime. + +::: warning + +If `proxy_ip` is not provided any user on the internal network can log in as any user in LibreTime. + +::: + +### Enable Header authentication + +After everything is set up properly you can enable header auth in `config.yml`: + +```yml +general: + auth: LibreTime_Auth_Adaptor_Header +``` + +You should now be automatically logged into LibreTime when you click the `Login` button. diff --git a/legacy/application/auth/adapters/HeaderAuth.php b/legacy/application/auth/adapters/HeaderAuth.php new file mode 100644 index 000000000..7bf2a0b5b --- /dev/null +++ b/legacy/application/auth/adapters/HeaderAuth.php @@ -0,0 +1,148 @@ +getHeaderValueOf($userHeader); + + if ($userLogin == null) { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null); + } + + $subj = CcSubjsQuery::create()->findOneByDbLogin($userLogin); + + if ($subj == null) { + $user = new Application_Model_User(''); + $user->setPassword(''); + $user->setLogin($userLogin); + } else { + $user = new Application_Model_User($subj->getDbId()); + } + + $name = $this->getHeaderValueOf($nameHeader); + + $user->setEmail($this->getHeaderValueOf($emailHeader)); + $user->setFirstName($this->getFirstName($name) ?? ''); + $user->setLastName($this->getLastName($name) ?? ''); + $user->setType($this->getUserType($this->getHeaderValueOf($groupsHeader))); + $user->save(); + $this->user = $user; + + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $user); + } + + private function getUserType(?string $groups): string + { + if ($groups == null) { + return UTYPE_GUEST; + } + + $groups = array_map(fn ($group) => trim($group), explode(',', $groups)); + + $superAdminGroup = Config::get('header_auth.group_map.superadmin'); + if (in_array($superAdminGroup, $groups)) { + return UTYPE_SUPERADMIN; + } + + $adminGroup = Config::get('header_auth.group_map.admin'); + if (in_array($adminGroup, $groups)) { + return UTYPE_ADMIN; + } + + $programManagerGroup = Config::get('header_auth.group_map.program_manager'); + if (in_array($programManagerGroup, $groups)) { + return UTYPE_PROGRAM_MANAGER; + } + + $hostGroup = Config::get('header_auth.group_map.host'); + if (in_array($hostGroup, $groups)) { + return UTYPE_HOST; + } + + return UTYPE_GUEST; + } + + private function getFirstName(?string $name): ?string + { + if ($name == null) { + return null; + } + + $result = explode(' ', $name, 2); + + return $result[0]; + } + + private function getLastName(?string $name): ?string + { + if ($name == null) { + return null; + } + + $result = explode(' ', $name, 2); + + return end($result); + } + + private function getHeaderValueOf(string $httpHeader): ?string + { + // Normalize the header name to match server's format + $normalizedHeader = 'HTTP_' . strtoupper(str_replace('-', '_', $httpHeader)); + + return $_SERVER[$normalizedHeader] ?? null; + } + + // Needed for zend auth adapter + + private Application_Model_User $user; + + public function setIdentity($username) + { + return $this; + } + + public function setCredential($password) + { + return $this; + } + + /** + * return dummy object for internal auth handling. + * + * we need to build a dummpy object since the auth layer knows nothing about the db + * + * @param null $returnColumns + * @param null $omitColumns + * + * @return stdClass + */ + public function getResultRowObject($returnColumns = null, $omitColumns = null) + { + $o = new stdClass(); + $o->id = $this->user->getId(); + $o->username = $this->user->getLogin(); + $o->password = $this->user->getPassword(); + $o->real_name = implode(' ', [$this->user->getFirstName(), $this->user->getLastName()]); + $o->type = $this->user->getType(); + $o->login = $this->user->getLogin(); + + return $o; + } +} diff --git a/legacy/application/configs/conf.php b/legacy/application/configs/conf.php index 229b85d08..e429ad609 100644 --- a/legacy/application/configs/conf.php +++ b/legacy/application/configs/conf.php @@ -99,6 +99,22 @@ class Schema implements ConfigurationInterface /**/->scalarNode('filter_field')->end() ->end()->end() + // Header Auth Schema + ->arrayNode('header_auth')->children() + /**/->scalarNode('user_header')->defaultValue('Remote-User')->end() + /**/->scalarNode('groups_header')->defaultValue('Remote-Groups')->end() + /**/->scalarNode('email_header')->defaultValue('Remote-Email')->end() + /**/->scalarNode('name_header')->defaultValue('Remote-Name')->end() + /**/->scalarNode('proxy_ip')->end() + /**/->scalarNode('locale')->defaultValue('en-US')->end() + /**/->arrayNode('group_map')->children() + /* */->scalarNode('host')->end() + /* */->scalarNode('program_manager')->end() + /* */->scalarNode('admin')->end() + /* */->scalarNode('superadmin')->end() + /**/->end()->end() + ->end()->end() + // Playout schema ->arrayNode('playout') /**/->ignoreExtraKeys() diff --git a/legacy/application/controllers/LoginController.php b/legacy/application/controllers/LoginController.php index 387f34e02..2d7469292 100644 --- a/legacy/application/controllers/LoginController.php +++ b/legacy/application/controllers/LoginController.php @@ -44,44 +44,37 @@ class LoginController extends Zend_Controller_Action $form = new Application_Form_Login(); - $message = _('Please enter your username and password.'); + $authAdapter = Application_Model_Auth::getAuthAdapter(); - if ($request->isPost()) { - // Open the session for writing, because we close it for writing by default in Bootstrap.php as an optimization. - // session_start(); + if ($authAdapter instanceof LibreTime_Auth_Adaptor_Header || ($request->isPost() && $form->isValid($request->getPost()))) { + // get the username and password from the form + $username = $form->getValue('username'); + $password = $form->getValue('password'); + $locale = $form->getValue('locale'); - if ($form->isValid($request->getPost())) { - // get the username and password from the form - $username = $form->getValue('username'); - $password = $form->getValue('password'); - $locale = $form->getValue('locale'); + // pass to the adapter the submitted username and password + $authAdapter->setIdentity($username) + ->setCredential($password); - $authAdapter = Application_Model_Auth::getAuthAdapter(); + $result = $auth->authenticate($authAdapter); + if ($result->isValid()) { + Zend_Session::regenerateId(); + // all info about this user from the login table omit only the password + $userInfo = $authAdapter->getResultRowObject(null, 'password'); - // pass to the adapter the submitted username and password - $authAdapter->setIdentity($username) - ->setCredential($password); + // the default storage is a session with namespace Zend_Auth + $authStorage = $auth->getStorage(); + $authStorage->write($userInfo); - $result = $auth->authenticate($authAdapter); - if ($result->isValid()) { - Zend_Session::regenerateId(); - // all info about this user from the login table omit only the password - $userInfo = $authAdapter->getResultRowObject(null, 'password'); + Application_Model_LoginAttempts::resetAttempts($_SERVER['REMOTE_ADDR']); + Application_Model_Subjects::resetLoginAttempts($username); - // the default storage is a session with namespace Zend_Auth - $authStorage = $auth->getStorage(); - $authStorage->write($userInfo); + // set the user locale in case user changed it in when logging in + Application_Model_Preference::SetUserLocale($locale); - Application_Model_LoginAttempts::resetAttempts($_SERVER['REMOTE_ADDR']); - Application_Model_Subjects::resetLoginAttempts($username); - - // set the user locale in case user changed it in when logging in - Application_Model_Preference::SetUserLocale($locale); - - $this->_redirect('showbuilder'); - } else { - $form = $this->loginError($username); - } + $this->_redirect('showbuilder'); + } else { + $form = $this->loginError($username); } }