src/Auth/Ldap.php
<?php
/**
* @author Nicolas CARPi <nico-git@deltablot.email>
* @copyright 2012 Nicolas CARPi
* @see https://www.elabftw.net Official website
* @license AGPL-3.0
* @package elabftw
*/
declare(strict_types=1);
namespace Elabftw\Auth;
use Elabftw\Elabftw\AuthResponse;
use Elabftw\Exceptions\ImproperActionException;
use Elabftw\Exceptions\InvalidCredentialsException;
use Elabftw\Exceptions\ResourceNotFoundException;
use Elabftw\Interfaces\AuthInterface;
use Elabftw\Models\ExistingUser;
use Elabftw\Models\ValidatedUser;
use Elabftw\Services\UsersHelper;
use LdapRecord\Connection;
use LdapRecord\Container;
use LdapRecord\Models\Entry;
use LdapRecord\Models\Model;
use LdapRecord\Query\ObjectNotFoundException;
use SensitiveParameter;
use function explode;
use function is_array;
/**
* LDAP auth service
*/
class Ldap implements AuthInterface
{
private AuthResponse $AuthResponse;
public function __construct(Connection $connection, private Entry $entries, private array $configArr, private string $login, #[SensitiveParameter] private string $password)
{
// add connection to the Container https://ldaprecord.com/docs/core/v3/connections/#container
$connection->connect();
Container::addConnection($connection);
$this->AuthResponse = new AuthResponse();
}
public function tryAuth(): AuthResponse
{
$record = $this->getRecord();
$dn = $record->getDn();
if ($dn === null) {
throw new ImproperActionException('Error finding the dn!');
}
if (!Container::getConnection()->auth()->attempt($dn, $this->password)) {
throw new InvalidCredentialsException(0);
}
// this->login can also be uid
$email = $this->getEmailFromRecord($record);
try {
$Users = ExistingUser::fromEmail($email);
} catch (ResourceNotFoundException) {
// the user doesn't exist yet in the db
// what do we do? Lookup the config setting for that case
if ($this->configArr['saml_user_default'] === '0') {
$msg = _('Could not find an existing user. Ask a Sysadmin to create your account.');
if ($this->configArr['user_msg_need_local_account_created']) {
$msg = $this->configArr['user_msg_need_local_account_created'];
}
throw new ImproperActionException($msg);
}
// GET FIRSTNAME AND LASTNAME
$firstname = $record[$this->configArr['ldap_firstname']][0] ?? 'Unknown';
$lastname = $record[$this->configArr['ldap_lastname']][0] ?? 'Unknown';
// GET TEAMS
$teamFromLdap = $record[$this->configArr['ldap_team']];
// the attribute is not found
if ($teamFromLdap === null) {
// we directly get the id from the stored config
$teamId = (int) $this->configArr['saml_team_default'];
if ($teamId === 0) {
throw new ImproperActionException('Could not find team ID to assign user!');
}
// this setting is when we want to allow the user to make team selection
if ($teamId === -1) {
$this->AuthResponse->userid = 0;
$this->AuthResponse->initTeamRequired = true;
$this->AuthResponse->initTeamUserInfo = array(
'email' => $email,
'firstname' => $firstname,
'lastname' => $lastname,
);
return $this->AuthResponse;
}
$teamFromLdap = array($teamId);
// it is found and it is a string
} elseif (is_string($teamFromLdap)) {
$teamFromLdap = array($teamFromLdap);
// it is found and it is an array
} elseif (is_array($teamFromLdap)) {
if (is_array($teamFromLdap[0])) {
// go one level deeper
$teamFromLdap = $teamFromLdap[0];
}
}
// ldap might return a "count" key, so we remove it or it will be interpreted as a team ID
unset($teamFromLdap['count']);
// CREATE USER (and force validation of user)
$Users = ValidatedUser::fromExternal($email, $teamFromLdap, $firstname, $lastname);
}
$this->AuthResponse->userid = $Users->userData['userid'];
$this->AuthResponse->mfaSecret = $Users->userData['mfa_secret'];
$this->AuthResponse->isValidated = (bool) $Users->userData['validated'];
$UsersHelper = new UsersHelper($this->AuthResponse->userid);
$this->AuthResponse->setTeams($UsersHelper);
return $this->AuthResponse;
}
// split the search attributes and search the user with them
private function getRecord(): Model
{
$attributes = explode(',', $this->configArr['ldap_search_attr']);
$this->entries->setDn($this->configArr['ldap_base_dn']);
foreach ($attributes as $attribute) {
try {
return $this->entries::findbyOrFail(trim($attribute), $this->login);
} catch (ObjectNotFoundException) {
continue;
}
}
throw new InvalidCredentialsException(0);
}
private function getEmailFromRecord(Model $record): string
{
// if the login input is the email, we have it already
if ($this->configArr['ldap_search_attr'] === 'mail') {
return $this->login;
}
$email = $record->getFirstAttribute($this->configArr['ldap_email']);
if ($email === null) {
throw new ImproperActionException('Could not find the mail attribute from the LDAP record.');
}
return $email;
}
}