dakriy f780994996
feat(legacy): add config option for group separator in header auth (#3181)
### Description

Not all forward auth solutions use a comma for group seperator.

**This is a new feature**:

Yes

**I have updated the documentation to reflect these changes**:

Yes

### **Links**

[Authentik uses `|` so may as well make the group separator
configurable](https://docs.goauthentik.io/docs/add-secure-apps/providers/proxy/)
2025-07-16 20:32:34 +02:00

151 lines
4.3 KiB
PHP

<?php
/**
* Auth adaptor for basic header authentication.
*/
class LibreTime_Auth_Adaptor_Header implements Zend_Auth_Adapter_Interface
{
/**
* @throws Exception
*/
public function authenticate(): Zend_Auth_Result
{
$trustedIp = Config::get('header_auth.proxy_ip');
if ($trustedIp != null && $_SERVER['REMOTE_ADDR'] != trim($trustedIp)) {
return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null);
}
$userHeader = Config::get('header_auth.user_header');
$groupsHeader = Config::get('header_auth.groups_header');
$emailHeader = Config::get('header_auth.email_header');
$nameHeader = Config::get('header_auth.name_header');
$userLogin = $this->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;
}
$separator = Config::get('header_auth.group_separator');
$groups = array_map(fn ($group) => trim($group), explode($separator, $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;
}
}