%PDF- %PDF-
| Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/humhub/modules/ldap/authclient/ |
| Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/humhub/modules/ldap/authclient/LdapAuth.php |
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2019 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\ldap\authclient;
use DateTime;
use humhub\libs\StringHelper;
use humhub\modules\ldap\Module;
use humhub\modules\user\authclient\AuthClientHelpers;
use humhub\modules\user\authclient\BaseFormAuth;
use humhub\modules\user\authclient\interfaces\ApprovalBypass;
use humhub\modules\user\authclient\interfaces\AutoSyncUsers;
use humhub\modules\user\authclient\interfaces\PrimaryClient;
use humhub\modules\user\authclient\interfaces\SyncAttributes;
use humhub\modules\user\models\forms\Login;
use humhub\modules\user\models\ProfileField;
use humhub\modules\user\models\User;
use Yii;
use yii\db\Expression;
use yii\helpers\ArrayHelper;
use yii\helpers\VarDumper;
use Laminas\Ldap\Exception\LdapException;
use Laminas\Ldap\Ldap;
use humhub\modules\ldap\components\ZendLdap;
use Laminas\Ldap\Node;
/**
* LDAP Authentication
*
* @todo create base ldap authentication, to bypass ApprovalByPass Interface
* @since 1.1
*/
class LdapAuth extends BaseFormAuth implements AutoSyncUsers, SyncAttributes, ApprovalBypass, PrimaryClient
{
/**
* @var Ldap
*/
private $_ldap = null;
/**
* @var string the auth client id
*/
public $clientId = 'ldap';
/**
* The hostname of LDAP server that these options represent. This option is required.
*
* @var string
*/
public $hostname;
/**
* The port on which the LDAP server is listening.
*
* @var int 389
*/
public $port;
/**
* Whether or not the LDAP client should use SSL encrypted transport.
* The useSsl and useStartTls options are mutually exclusive, but useStartTls should be favored
* if the server and LDAP client library support it.
*
* @var boolean
*/
public $useSsl = false;
/**
* Whether or not the LDAP client should use TLS (aka SSLv2) encrypted transport.
* A value of TRUE is strongly favored in production environments to prevent passwords from be transmitted in clear text.
*
* The default value is FALSE, as servers frequently require that a certificate be installed separately after installation.
* The useSsl and useStartTls options are mutually exclusive.
* The useStartTls option should be favored over useSsl but not all servers support this newer mechanism.
*
* @var boolean
*/
public $useStartTls = false;
/**
* The DN of the account used to perform account DN lookups.
* LDAP servers that require the username to be in DN form when performing the “bind” require this option.
*
* @var string
*/
public $bindUsername;
/**
* The password of the account used to perform account DN lookups.
*
* @var string
*/
public $bindPassword;
/**
* ID attribute to uniquely identify user.
* If set to null, automatically a value email or objectguid will be used if available.
*
* @var string attribute name to identify node
*/
public $idAttribute = null;
/**
* @var string the email attribute
*/
public $emailAttribute = null;
/**
* @var string the ldap username attribute
*/
public $usernameAttribute = null;
/**
* @var string the ldap base dn
*/
public $baseDn = null;
/**
* @var string the ldap query to find humhub users
*/
public $userFilter = null;
/**
* The LDAP search filter used to search for accounts.
* This string is a printf()-style expression that must contain one ‘%s’ to accommodate the username.
*
* @var string the login filter
*/
public $loginFilter = null;
/**
* Automatically refresh user profiles on cron run
*
* @var boolean|null
*/
public $autoRefreshUsers = null;
/**
* @inheritdoc
*/
public $byPassApproval = true;
/**
* @var array of attributes which are synced with the user table
*/
public $syncUserTableAttributes = ['username', 'email'];
/**
* @var int The value for network timeout when connect to the LDAP server.
*/
public $networkTimeout = 30;
/**
* @var string[] a list of ignored DNs (lowercase)
* @since 1.9
*/
public $ignoredDNs = [];
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if (empty($this->idAttribute)) {
$this->idAttribute = null;
}
$this->idAttribute = strtolower($this->idAttribute);
if (empty($this->usernameAttribute)) {
$this->usernameAttribute = 'samaccountname';
}
$this->usernameAttribute = strtolower($this->usernameAttribute);
if (empty($this->emailAttribute)) {
$this->emailAttribute = 'mail';
}
$this->emailAttribute = strtolower($this->emailAttribute);
}
/**
* @inheritdoc
*/
public function getId()
{
return $this->clientId;
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return $this->clientId;
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'LDAP (' . $this->clientId . ')';
}
/**
* @inheritdoc
*/
public function getIdAttribute()
{
return $this->idAttribute;
}
/**
* Find user based on ldap attributes
*
* @inheritdoc
* @return User the user
* @see PrimaryClient
*/
public function getUser()
{
$attributes = $this->getUserAttributes();
// Try to load user by ldap id attribute
if ($this->idAttribute !== null && isset($attributes['authclient_id'])) {
$user = User::findOne(['authclient_id' => $attributes['authclient_id'], 'auth_mode' => $this->getId()]);
if ($user !== null) {
return $user;
}
}
return $this->getUserAuto();
}
/**
* Try to find the user if authclient_id mapping is not set yet (legency)
* or idAttribute is not specified.
*
* @return User
*/
protected function getUserAuto()
{
$attributes = $this->getUserAttributes();
// Try to find user user if authclient_id is null based on ldap fields objectguid and e-mail
$query = User::find();
$query->where(['auth_mode' => $this->getId()]);
if ($this->idAttribute !== null) {
$query->andWhere(['IS', 'authclient_id', new Expression('NULL')]);
}
$conditions = ['OR'];
if (isset($attributes['email']) && !empty($attributes['email'])) {
$conditions[] = ['email' => $attributes['email']];
}
if (isset($attributes['objectguid']) && !empty($attributes['objectguid'])) {
$conditions[] = ['guid' => $attributes['objectguid']];
}
if (isset($attributes['uid']) && !empty($attributes['uid'])) {
$conditions[] = ['username' => $attributes['uid']];
}
if ($conditions)
$query->andWhere($conditions);
return $query->one();
}
/**
* @inheritdoc
*/
public function auth()
{
$node = $this->getUserNode();
if ($node !== null) {
$this->setUserAttributes(array_merge(['dn' => $node], $node->getAttributes()));
return true;
} else if ($this->login instanceof Login) {
$this->countFailedLoginAttempts();
}
return false;
}
/**
* @inheritdoc
*/
protected function defaultNormalizeUserAttributeMap()
{
$map = [];
$map['username'] = $this->usernameAttribute;
$map['email'] = $this->emailAttribute;
// Profile Field Mapping
foreach (ProfileField::find()->andWhere(['!=', 'ldap_attribute', ''])->all() as $profileField) {
$map[$profileField->internal_name] = strtolower($profileField->ldap_attribute);
}
return $map;
}
/**
* @inheritdoc
*/
protected function normalizeUserAttributes($attributes)
{
$normalized = [];
// Fix LDAP Attributes
foreach ($attributes as $name => $value) {
if (is_array($value) && !in_array($name, ['memberof', 'ismemberof'])) {
if (isset($value[0])) {
$normalized[$name] = $value[0];
}
} else {
$normalized[$name] = $value;
}
}
if (isset($normalized['objectguid'])) {
$normalized['objectguid'] = StringHelper::binaryToGuid($normalized['objectguid']);
}
// Handle date fields (formats are specified in config)
foreach ($normalized as $name => $value) {
if (isset(Yii::$app->params['ldap']['dateFields'][$name]) && $value != '') {
$dateFormat = Yii::$app->params['ldap']['dateFields'][$name];
$date = DateTime::createFromFormat($dateFormat, $value);
if ($date !== false) {
$normalized[$name] = $date->format('Y-m-d');
} else {
$normalized[$name] = '';
}
}
}
if ($this->idAttribute !== null && isset($normalized[$this->idAttribute])) {
$normalized['authclient_id'] = $normalized[$this->idAttribute];
}
$normalized['id'] = 'unused';
return parent::normalizeUserAttributes($normalized);
}
/**
* @return array list of user attributes
*/
public function getUserAttributes()
{
$attributes = parent::getUserAttributes();
// Make sure id attributes sits on id attribute key
if (isset($attributes[$this->getIdAttribute()])) {
$attributes['id'] = $attributes[$this->getIdAttribute()];
}
return $attributes;
}
/**
* Returns Users LDAP Node
*
* @return Node the users ldap node
* @throws LdapException
*/
protected function getUserNode()
{
$dn = $this->getUserDn();
if ($dn !== '') {
return $this->getLdap()->getNode($dn);
}
return null;
}
/**
* Returns the users LDAP DN
*
* @return string the user dn if found
*/
protected function getUserDn()
{
$userName = $this->login->username;
// Translate given e-mail to username
if (strpos($userName, '@') !== false) {
$user = User::findOne(['email' => $userName]);
if ($user !== null) {
$userName = $user->username;
}
}
try {
$this->getLdap()->bind($userName, $this->login->password);
// Rebind with administrative DN
$this->getLdap()->bind();
$dn = $this->getLdap()->getCanonicalAccountName($userName, Ldap::ACCTNAME_FORM_DN);
return $dn;
} catch (LdapException $ex) {
// User not found in LDAP
}
return '';
}
/**
* Returns Zend LDAP
*
* @return ZendLdap
* @throws LdapException
*/
public function getLdap()
{
if ($this->_ldap === null) {
$options = [
'host' => $this->hostname,
'port' => $this->port,
'username' => $this->bindUsername,
'password' => $this->bindPassword,
'useStartTls' => $this->useStartTls,
'useSsl' => $this->useSsl,
'bindRequiresDn' => true,
'baseDn' => $this->baseDn,
'accountFilterFormat' => $this->loginFilter,
'networkTimeout' => $this->networkTimeout,
];
$this->_ldap = new ZendLdap($options);
$this->_ldap->bind();
}
return $this->_ldap;
}
/**
* Sets an Zend LDAP Instance
*
* @param \Laminas\Ldap\Ldap $ldap
*/
public function setLdap(Ldap $ldap)
{
$this->_ldap = $ldap;
}
/**
* @inheritdoc
*/
public function getSyncAttributes()
{
$attributes = $this->syncUserTableAttributes;
$attributes[] = 'authclient_id';
foreach (ProfileField::find()->andWhere(['!=', 'ldap_attribute', ''])->all() as $profileField) {
$attributes[] = $profileField->internal_name;
}
return $attributes;
}
/**
* Refresh ldap users
*
* New users (found in ldap) will be automatically created if all required fiélds are set.
* Profile fields which are bind to LDAP will automatically updated.
*/
public function syncUsers()
{
if ($this->autoRefreshUsers !== true) {
return;
}
try {
$authClient = null;
$ids = [];
foreach ($this->getUserCollection() as $ldapEntry) {
if (in_array(strtolower($ldapEntry['dn']), $this->ignoredDNs)) {
continue;
}
$authClient = $this->getAuthClientInstance($ldapEntry);
$user = AuthClientHelpers::getUserByAuthClient($authClient);
if ($user === null) {
$registration = AuthClientHelpers::createRegistration($authClient);
if ($registration === null) {
Yii::warning('Could not automatically create LDAP user - No ID attribute!', 'ldap');
continue;
}
if (!$registration->register($authClient)) {
Yii::warning('Could not create LDAP user (' . $ldapEntry['dn'] . '). Error: '
. VarDumper::dumpAsString($registration->getErrors()), 'ldap');
}
} else {
AuthClientHelpers::updateUser($authClient, $user);
}
$attributes = $authClient->getUserAttributes();
if (isset($attributes['authclient_id'])) {
$ids[] = $attributes['authclient_id'];
}
}
// Disable or Reenable Users based on collected $ids Arrays
// This is only possible if a unique id attribute is specified.
if ($this->idAttribute !== null) {
foreach (AuthClientHelpers::getUsersByAuthClient($this)->each() as $user) {
$foundInLdap = in_array($user->authclient_id, $ids);
if ($foundInLdap && $user->status === User::STATUS_DISABLED) {
// Enable disabled users that have been found in ldap
$user->status = User::STATUS_ENABLED;
$user->save();
Yii::info('Enabled user' . $user->username . ' (' . $user->id . ') - found in LDAP!', 'ldap');
} elseif (!$foundInLdap && $user->status == User::STATUS_ENABLED) {
// Disable users that were not found in ldap
$user->status = User::STATUS_DISABLED;
$user->save();
Yii::warning('Disabled user' . $user->username . ' (' . $user->id . ') - not found in LDAP!', 'ldap');
}
}
}
} catch (\Laminas\Ldap\Exception\LdapException $ex) {
Yii::error('Could not connect to LDAP instance: ' . $ex->getMessage(), 'ldap');
} catch (\Exception $ex) {
Yii::error('An error occurred while user sync: ' . $ex->getMessage(), 'ldap');
}
}
/**
* @param array $normalizeUserAttributeMap normalize user attribute map.
*/
public function setNormalizeUserAttributeMap($normalizeUserAttributeMap)
{
// This method is called if an additional attribute mapping is specified in the configuration file
// So automatically merge HumHub auto mapping with the given one
$this->init(); // defaultNormalizeAttributeMap is available after init
parent::setNormalizeUserAttributeMap(ArrayHelper::merge($this->defaultNormalizeUserAttributeMap(), $normalizeUserAttributeMap));
}
/**
* @return array
* @throws LdapException
*/
public function getUserCollection()
{
/** @var Module $module */
$module = Yii::$app->getModule('ldap');
if (empty($module->pageSize)) {
return $this->getLdap()->search($this->userFilter, $this->baseDn, Ldap::SEARCH_SCOPE_SUB, $module->queriedAttributes);
}
return $this->getLdap()->multiPageSearch($this->userFilter, $this->baseDn, Ldap::SEARCH_SCOPE_SUB, $module->queriedAttributes, null, null, 0, $module->pageSize);
}
/**
* @param $ldapEntry array
* @return LdapAuth
*/
public function getAuthClientInstance($ldapEntry)
{
$authClient = clone $this;
$authClient->init();
$authClient->setUserAttributes($ldapEntry);
// Init
$attributes = $authClient->getUserAttributes();
return $authClient;
}
}