From 330ec4f207e7a0fe995af60963cd202e6fc66a55 Mon Sep 17 00:00:00 2001 From: Andrea Scaramucci Date: Tue, 5 Mar 2024 18:14:36 +0100 Subject: [PATCH 1/5] Added user autocomplete from ldap in create view and a parameter do enable it --- src/User/Dictionary/UserSourceType.php | 37 +++++++++++++++ src/User/Module.php | 4 ++ src/User/resources/views/admin/_user.php | 58 +++++++++++++++++++++-- src/User/resources/views/admin/create.php | 8 +++- 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 src/User/Dictionary/UserSourceType.php diff --git a/src/User/Dictionary/UserSourceType.php b/src/User/Dictionary/UserSourceType.php new file mode 100644 index 00000000..bf2cd801 --- /dev/null +++ b/src/User/Dictionary/UserSourceType.php @@ -0,0 +1,37 @@ + \Yii::t('usuario', 'Local'), + static::LDAP => \Yii::t('usuario', 'LDAP'), + ]; + } + + /** + * Returns the dictionary value for the given code + * @param $key + * @return string|null + * @throws \Exception + */ + public static function get($key) + { + return ArrayHelper::getValue(static::all(), $key); + } + + +} diff --git a/src/User/Module.php b/src/User/Module.php index d8b4e03d..87929ad4 100755 --- a/src/User/Module.php +++ b/src/User/Module.php @@ -32,6 +32,10 @@ class Module extends BaseModule * if equals false records will not be deleted */ public $numberSessionHistory = false; + /** + * @var bool Enable LDAP to sync local users with LDAP users + */ + public $searchUsersInLdap = false; /** * @var int|bool The time after which the expired 'session history' will be deleted * if equals false records will not be deleted diff --git a/src/User/resources/views/admin/_user.php b/src/User/resources/views/admin/_user.php index 5e7303e5..97bf36e2 100644 --- a/src/User/resources/views/admin/_user.php +++ b/src/User/resources/views/admin/_user.php @@ -13,8 +13,60 @@ * @var yii\widgets\ActiveForm $form * @var \Da\User\Model\User $user */ + +use Da\User\Dictionary\UserSourceType; +use kartik\typeahead\Typeahead; +use yii\helpers\Html; +use yii\helpers\Url; + ?> +request->get('source') ?: $user->source ?> -field($user, 'email')->textInput(['maxlength' => 255]) ?> -field($user, 'username')->textInput(['maxlength' => 255]) ?> -field($user, 'password')->passwordInput() ?> +registerJs(<< +isNewRecord): ?> + getModule('user')->searchUsersInLdap && $source==UserSourceType::LDAP): ?> + field($user, 'source')->dropDownList(UserSourceType::all(), ['value' => $source]); ?> + field($user, 'email')->widget(Typeahead::class, [ + 'options' => ['placeholder' => Yii::t('mis', 'Filter as you type ...')], + 'pluginOptions' => ['highlight' => true], + 'dataset' => [ + [ + 'display' => 'value', + 'remote' => [ + 'url' => Url::to(['/usuario-ldap/ldap/search']) . '?q=%QUERY', // You can add &limit to set a results limit, 20 to default + 'wildcard' => '%QUERY' + ] + ] + ], + // When the email is selected, get the username and change the source from local to ldap + 'pluginEvents' => [ + 'typeahead:select' => 'updateFromLdap', + ], + ]) + ?> + field($user, 'username')->hiddenInput()->label(false) ?> + + field($user, 'source')->dropDownList(UserSourceType::all(), ['value' => $source]); ?> + field($user, 'email')->textInput(['maxlength' => 255]) ?> + field($user, 'username')->textInput(['maxlength' => 255]) ?> + field($user, 'password')->passwordInput() ?> + + + field($user, 'email')->textInput(['maxlength' => 255]) ?> + field($user, 'username')->textInput(['maxlength' => 255]) ?> + field($user, 'password')->passwordInput() ?> + diff --git a/src/User/resources/views/admin/create.php b/src/User/resources/views/admin/create.php index f65d7de1..ee20fd71 100644 --- a/src/User/resources/views/admin/create.php +++ b/src/User/resources/views/admin/create.php @@ -12,6 +12,7 @@ use yii\bootstrap\ActiveForm; use yii\bootstrap\Nav; use yii\helpers\Html; +use yii\widgets\Pjax; /** * @var yii\web\View $this @@ -83,7 +84,9 @@ 'A password will be generated automatically if not provided' ) ?>. - 'pjax-user-create']); + $form = ActiveForm::begin( [ 'layout' => 'horizontal', 'enableAjaxValidation' => true, @@ -107,7 +110,8 @@ - + From df559b1ea8f9e057229f3f06b2320cd78aae7609 Mon Sep 17 00:00:00 2001 From: Andrea Scaramucci Date: Wed, 13 Mar 2024 12:22:23 +0100 Subject: [PATCH 2/5] Added user and profile ldap sync to user creation --- docs/install/configuration-options.md | 26 +++++++ src/User/Bootstrap.php | 6 ++ src/User/Dictionary/UserSourceType.php | 6 +- src/User/Model/User.php | 34 ++++++++++ src/User/Module.php | 13 +++- src/User/Service/InitLdapUserService.php | 43 ++++++++++++ src/User/resources/i18n/it/usuario.php | 42 ++++++------ src/User/resources/views/admin/_user.php | 86 +++++++++++------------- 8 files changed, 185 insertions(+), 71 deletions(-) create mode 100644 src/User/Service/InitLdapUserService.php diff --git a/docs/install/configuration-options.md b/docs/install/configuration-options.md index ba775e1c..72f61cb9 100755 --- a/docs/install/configuration-options.md +++ b/docs/install/configuration-options.md @@ -14,6 +14,32 @@ Number of expired storing records `session history`, values: - `false` Store all records without deleting - `integer` Count of records for storing +#### searchUsersInLdap (Type: `boolean, integer`, Default value: `false`) + +If this option is `true`, it will be possible to search users in LDAP. + +To use this option you need to install kartik typeahead: + +```shell +composer require kartik-v/yii2-widget-typeahead "dev-master" +``` + +#### ldapUserAttributes (Type: `array`) + +This array maps the user attributes to sync from ldap + +Default to: +```php +[ + 'email' => 'mail', + 'username' => 'samaccountname', +] +``` + +#### ldapProfileAttributes (Type: `array`) + +This array maps the profile attributes to sync from ldap + #### timeoutSessionHistory (Type: `boolean, integer`, Default value: `false`) How long store `session history` after expiring, values: diff --git a/src/User/Bootstrap.php b/src/User/Bootstrap.php index 1b92e446..b1f9c336 100755 --- a/src/User/Bootstrap.php +++ b/src/User/Bootstrap.php @@ -154,6 +154,12 @@ function () use ($model) { $di->set(Search\RoleSearch::class); } + if (Yii::$app->getModule('user')->searchUsersInLdap) { + if (!class_exists('kartik\typeahead\Typeahead')) { + throw new InvalidConfigException('The kartik-v/yii2-widget-typeahead library must be defined when searchUsersInLdap is true.'); + } + } + // Attach an event to check if the password has expired if (null !== Yii::$app->getModule('user')->maxPasswordAge) { YiiEvent::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN, function (FormEvent $event) { diff --git a/src/User/Dictionary/UserSourceType.php b/src/User/Dictionary/UserSourceType.php index bf2cd801..4e94d1cc 100644 --- a/src/User/Dictionary/UserSourceType.php +++ b/src/User/Dictionary/UserSourceType.php @@ -6,8 +6,8 @@ class UserSourceType { - const LOCAL = 'LOCAL'; - const LDAP = 'LDAP'; + public const LOCAL = 'LOCAL'; + public const LDAP = 'LDAP'; /** * Returns an array that contains the codes for the dictionary and their common name. It's useful to be used for @@ -17,7 +17,7 @@ class UserSourceType public static function all() { return [ - static::LOCAL => \Yii::t('usuario', 'Local'), + static::LOCAL => \Yii::t('usuario', 'Local'), static::LDAP => \Yii::t('usuario', 'LDAP'), ]; } diff --git a/src/User/Model/User.php b/src/User/Model/User.php index 1e60d71b..57370b44 100644 --- a/src/User/Model/User.php +++ b/src/User/Model/User.php @@ -11,10 +11,14 @@ namespace Da\User\Model; +use Da\User\Dictionary\UserSourceType; use Da\User\Helper\SecurityHelper; use Da\User\Query\UserQuery; +use Da\User\Service\InitLdapUserService; use Da\User\Traits\ContainerAwareTrait; use Da\User\Traits\ModuleAwareTrait; +use lhs\Yii2SaveRelationsBehavior\SaveRelationsBehavior; +use yetopen\usuarioLdap\UsuarioLdapComponent; use Yii; use yii\base\Exception; use yii\base\InvalidConfigException; @@ -78,6 +82,10 @@ class User extends ActiveRecord implements IdentityInterface * @var array connected account list */ protected $connectedAccounts; + /** + * @var \Adldap\Models\User|null + */ + protected $ldapUser; /** * {@inheritdoc} @@ -127,6 +135,22 @@ public static function findIdentityByAccessToken($token, $type = null) throw new NotSupportedException('Method "' . __CLASS__ . '::' . __METHOD__ . '" is not implemented.'); } + /** + * {@inheritdoc} + */ + public function beforeValidate() + { + if ($this->module->searchUsersInLdap && $this->source == UserSourceType::LDAP) { + /** @var UsuarioLdapComponent $ldapComponent */ + $ldapComponent = Yii::$app->usuarioLdap; + $this->ldapUser = $ldapComponent->findLdapUser($this->email); + if ($this->ldapUser !== null) { + $this->make(InitLdapUserService::class, [$this, $this->module->ldapUserAttributes, $this->ldapUser])->run(); + } + } + return parent::beforeValidate(); + } + /** * {@inheritdoc} */ @@ -163,6 +187,9 @@ public function afterSave($insert, $changedAttributes) if ($insert && $this->profile === null) { $profile = $this->make(Profile::class); + if ($this->ldapUser !== null) { + $this->make(InitLdapUserService::class, [$profile, $this->module->ldapProfileAttributes, $this->ldapUser])->run(); + } $profile->link('user', $this); } } @@ -184,6 +211,13 @@ public function behaviors() ]; } + $behaviors['saveRelations'] = [ + 'class' => SaveRelationsBehavior::class, + 'relations' => [ + 'profile' => ['cascadeDelete' => true] + ] + ]; + return $behaviors; } diff --git a/src/User/Module.php b/src/User/Module.php index 87929ad4..7dd7a62a 100755 --- a/src/User/Module.php +++ b/src/User/Module.php @@ -33,9 +33,20 @@ class Module extends BaseModule */ public $numberSessionHistory = false; /** - * @var bool Enable LDAP to sync local users with LDAP users + * @var bool If this option is `true`, it will be possible to search users in LDAP */ public $searchUsersInLdap = false; + /** + * @var array user attributes to sync from ldap + */ + public $ldapUserAttributes = [ + 'email' => 'mail', + 'username' => 'samaccountname', + ]; + /** + * @var array profile attributes to sync from ldap + */ + public $ldapProfileAttributes = []; /** * @var int|bool The time after which the expired 'session history' will be deleted * if equals false records will not be deleted diff --git a/src/User/Service/InitLdapUserService.php b/src/User/Service/InitLdapUserService.php new file mode 100644 index 00000000..f64dbfa5 --- /dev/null +++ b/src/User/Service/InitLdapUserService.php @@ -0,0 +1,43 @@ +model = $model; + $this->attributes = $attributes; + $this->ldapUser = $ldapUser; + } + + /** + * @inheritDoc + */ + public function run() + { + foreach ($this->attributes as $attribute => $ldapAttribute) { + // if Closure call and assign + if ($ldapAttribute instanceof \Closure) { + $this->model->$attribute = $ldapAttribute($this->ldapUser, $attribute); + continue; + } + $value = $this->ldapUser->$ldapAttribute; + if (empty($value)) { + continue; + } + $this->model->$attribute = ArrayHelper::getValue($value, 0); + } + } +} diff --git a/src/User/resources/i18n/it/usuario.php b/src/User/resources/i18n/it/usuario.php index 668cf106..8bcb2892 100644 --- a/src/User/resources/i18n/it/usuario.php +++ b/src/User/resources/i18n/it/usuario.php @@ -1,5 +1,4 @@ 'Dettagli account', 'Account details have been updated' => 'I dettagli del tuo account sono stati aggiornati', 'Account settings' => 'Impostazioni account', + 'Active' => 'Attivo', 'Already registered? Sign in!' => 'Già registrato? Accedi!', 'An email with instructions to create a new password has been sent to {email} if it is associated with an {appName} account. Your existing password has not been changed.' => 'Una mail con le istruzioni per creare una nuova password è stata inviata all\'indirizzo {email} se associato a un account {appName}. La tua password non è ancora stata cambiata.', 'An error occurred processing your request' => 'Si è verificato un errore durante l\'elaborazione della richiesta', + 'Application not configured for two factor authentication.' => 'Autenticazione a due fattori (2FA) non abilitata per l\'applicazione', 'Are you sure you want to block this user?' => 'Sicuro di voler bloccare questo utente?', 'Are you sure you want to confirm this user?' => 'Sicuro di voler confermare questo utente?', 'Are you sure you want to delete this user?' => 'Sicuro di voler eliminare questo utente?', @@ -53,12 +54,12 @@ 'Authorization rule has been updated.' => 'Regola di autorizzazione modificata.', 'Awesome, almost there. Now you need to click the confirmation link sent to your new email address.' => 'Fantastico, ci siamo quasi. Ora devi solo visitare il collegamento di conferma che è stato inviato al tuo nuovo indirizzo email.', 'Awesome, almost there. Now you need to click the confirmation link sent to your old email address.' => 'Fantastico, ci siamo quasi. Ora devi solo visitare il collegamento di conferma che è stato inviato al tuo vecchio indirizzo email.', - 'Can\'t scan? Copy the code instead.' => 'Non puoi scansionare? Copia il codice.', 'Back to privacy settings' => 'Torna alle impostazioni di privacy', 'Bio' => 'Bio', 'Block' => 'Blocca', 'Block status' => 'Stato di blocco', 'Blocked at {0, date, MMMM dd, YYYY HH:mm}' => 'Bloccato il {0, date, dd MMMM YYYY HH:mm}', + 'Can\'t scan? Copy the code instead.' => 'Non puoi scansionare? Copia il codice.', 'Cancel' => 'Annulla', 'Cannot assign role "{0}" as the AuthManager is not configured on your console application.' => 'Impossibile assegnare il ruolo "{0}" perché l\'AuthManager non è configurato nella applicazione da console.', 'Change your avatar at Gravatar.com' => 'Modifica la tua immagine di profilo su Gravatar.com', @@ -84,6 +85,7 @@ 'Create new rule' => 'Crea nuova regola', 'Created at' => 'Creata il', 'Credentials will be sent to the user by email' => 'Le credenziali verranno inviate all\'utente via email', + 'Current' => 'Attuale', 'Current password' => 'Password attuale', 'Current password is not valid' => 'La password attuale non è valida', 'Data privacy' => 'Data privacy', @@ -118,13 +120,16 @@ 'Hello' => 'Ciao', 'Here you can download your personal data in a comma separated values format.' => 'Da qui puoi scaricare i tuoi dati in formato CSV.', 'I agree processing of my personal data and the use of cookies to facilitate the operation of this site. For more information read our {privacyPolicy}' => 'Consento al trattamento dei miei dati personali e all\'uso dei cookie per agevolare le attività di questo sito. Per ulteriori informazioni leggere la nostra {privacyPolicy}', + 'IP' => 'IP', 'If you already registered, sign in and connect this account on settings page' => 'Se sei già registrato accedi e collega questo account nella pagina delle impostazioni', 'If you cannot click the link, please try pasting the text into your browser' => 'Se non puoi fare click sul link prova a copiare ed incollare il testo nel browser', 'If you did not make this request you can ignore this email' => 'Se non hai effettuato tu la richiesta puoi ignorare questa email', + 'If you haven\'t received a password, you can reset it at' => 'Se non hai ricevuto una password, puoi reimpostarla su', 'Impersonate this user' => 'Impersona questo utente', 'In order to complete your registration, please click the link below' => 'Per completare la registrazione fai click sul collegamento qui sotto', 'In order to complete your request, please click the link below' => 'Per completare la richiesta fai click sul collegamento qui sotto', 'In order to finish your registration, we need you to enter following fields' => 'Per finalizzare la registrazione devi fornire le seguenti informazioni', + 'Inactive' => 'Inattivo', 'Information' => 'Informazioni', 'Insert' => 'Inserisci', 'Insert the code you received by SMS.' => 'Inserisci il codice ricevuto tramite SMS.', @@ -138,6 +143,7 @@ 'It will be deleted forever' => 'Sarà eliminato per sempre', 'Items' => 'Elementi', 'Joined on {0, date}' => 'Registrato il {0, date}', + 'Last activity' => 'Ultima attività', 'Last login IP' => 'IP ultimo accesso', 'Last login time' => 'Data ultimo accesso', 'Last password change' => 'Data cambio password', @@ -200,11 +206,15 @@ 'Scan the QrCode with Google Authenticator App, then insert its temporary code on the box and submit.' => 'Scansiona il codice QR con l\'applicazione Google Authenticator, poi inserisci il codice temporaneo nel riquadro ed invia.', 'Select rule...' => 'Seleziona una regola...', 'Send password recovery email' => 'Invia email di recupero password', + 'Session ID' => 'ID sessione', + 'Session history' => 'Cronologia sessioni', 'Sign in' => 'Accedi', 'Sign up' => 'Registrati', 'Something went wrong' => 'È successo qualcosa di strano', + 'Status' => 'Stato', 'Submit' => 'Invia', 'Switch identities is disabled.' => 'Il cambio identità è disabilitato', + 'Terminate all sessions' => 'Termina tutte le sessioni', 'Text message' => 'Messaggio di testo tramite SMS', 'Thank you for signing up on {0}' => 'Grazie per esserti registrato su {0}', 'Thank you, registration is now complete.' => 'Grazie, la tua registrazione è completa.', @@ -213,12 +223,14 @@ 'The email address set is: "{0}".' => 'L\'indirizzo email impostato è: "{0}".', 'The email sending failed, please check your configuration.' => 'L\'invio della email non è riuscito, verifica la configurazione', 'The phone number set is: "{0}".' => 'Il numero di telefono impostato è: "{0}".', + 'The requested page does not exist.' => 'La pagina richiesta non esiste.', 'The sms sending failed, please check your configuration.' => 'L\'invio del messaggio di testo non è riuscito, verifica il numero di cellulare o contatta l\'assistenza', 'The verification code is incorrect.' => 'Il codice di verifica non è corretto.', 'There is neither role nor permission with name "{0}"' => 'Non esiste un ruolo o permesso di nome "{0}', 'There was an error in saving user' => 'Errore in salvataggio utente', 'This account has already been connected to another user' => 'Questo account è già stato associato ad un altro utente', 'This email address has already been taken' => 'Questo indirizzo email è già stato registrato', + 'This is the code to insert to enable two factor authentication' => 'Questo è il codice da inserire per abilitare l\'autenticazione a due fattori', 'This username has already been taken' => 'Questo nome utente è già stato registrato', 'This will disable two factor authentication. Are you sure?' => 'Stai per disabilitare l\'autenticazione a due fattori. Sei sicuro?', 'This will remove your personal data from this site. You will no longer be able to sign in.' => 'Questa operazione rimuoverà i tuoi dati personali da questo sito. Non ti sarà più possibile effettuare l\'accesso.', @@ -253,9 +265,12 @@ 'Update rule' => 'Modifica regola', 'Update user account' => 'Modifica account utente', 'Updated at' => 'Aggiornata il', + 'User ID' => 'ID utente', 'User account could not be created.' => 'Impossibile creare il nuovo utente.', + 'User agent' => 'User agent', 'User block status has been updated.' => 'Stato di blocco aggiornato.', 'User could not be registered.' => 'Impossibile registrare l\'utente.', + 'User does not have sufficient permissions.' => 'L\'utente non ha i permessi per accedere.', 'User has been confirmed' => 'L\'utente è stato confermato', 'User has been created' => 'L\'utente è stato creato', 'User has been deleted' => 'L\'utente è stato eliminato', @@ -277,8 +292,11 @@ 'You are about to delete all your personal data from this site.' => 'Stai per eliminare tutti i tuoi dati personali da questo sito.', 'You can assign multiple roles or permissions to user by using the form below' => 'Puoi assegnare più permessi o ruoli all\'utente usando il form sotto', 'You can connect multiple accounts to be able to log in using them' => 'Puoi collegare account esterni e fare login con quelli', + 'You cannot block your own account.' => 'Non puoi bloccare il tuo stesso account.', 'You cannot remove your own account' => 'Non puoi eliminare il tuo account', + 'You cannot remove your own account.' => 'Non puoi eliminare il tuo stesso account.', 'You need to confirm your email address' => 'Devi confermare il tuo indirizzo email', + 'You received this email because someone, possibly you or someone on your behalf, have created an account at {app_name}' => 'Hai ricevuto questa email perché qualcuno, presumibilmente tu, ha creato un account su {app_name}', 'Your account details have been updated' => 'I dettagli del tuo account sono stati aggiornati', 'Your account has been blocked' => 'Il tuo account è stato bloccato', 'Your account has been blocked.' => 'Il tuo account è stato bloccato.', @@ -300,23 +318,5 @@ '{0, date, MMM dd, YYYY HH:mm}' => '{0, date, MMM dd, YYYY HH:mm}', '{0, date, MMMM dd, YYYY HH:mm}' => '{0, date, dd MMMM YYYY HH:mm}', '{0} cannot be blank.' => '{0} non può essere vuoto.', - 'Active' => 'Attivo', - 'Application not configured for two factor authentication.' => 'Autenticazione a due fattori (2FA) non abilitata per l\'applicazione', - 'Current' => 'Attuale', - 'IP' => 'IP', - 'If you haven\'t received a password, you can reset it at' => 'Se non hai ricevuto una password, puoi reimpostarla su', - 'Inactive' => 'Inattivo', - 'Last activity' => 'Ultima attività', - 'Session ID' => 'ID sessione', - 'Session history' => 'Cronologia sessioni', - 'Status' => 'Stato', - 'Terminate all sessions' => 'Termina tutte le sessioni', - 'The requested page does not exist.' => 'La pagina richiesta non esiste.', - 'This is the code to insert to enable two factor authentication' => 'Questo è il codice da inserire per abilitare l\'autenticazione a due fattori', - 'User ID' => 'ID utente', - 'User agent' => 'User agent', - 'User does not have sufficient permissions.' => 'L\'utente non ha i permessi per accedere.', - 'You cannot block your own account.' => 'Non puoi bloccare il tuo stesso account.', - 'You cannot remove your own account.' => 'Non puoi eliminare il tuo stesso account.', - 'You received this email because someone, possibly you or someone on your behalf, have created an account at {app_name}' => 'Hai ricevuto questa email perché qualcuno, presumibilmente tu, ha creato un account su {app_name}', + 'Filter as you type...' => 'Digita per filtrare...', ]; diff --git a/src/User/resources/views/admin/_user.php b/src/User/resources/views/admin/_user.php index 97bf36e2..84870062 100644 --- a/src/User/resources/views/admin/_user.php +++ b/src/User/resources/views/admin/_user.php @@ -19,54 +19,48 @@ use yii\helpers\Html; use yii\helpers\Url; -?> -request->get('source') ?: $user->source ?> +$source = Yii::$app->request->get('source') ?: $user->source; -registerJs(<< -isNewRecord): ?> - getModule('user')->searchUsersInLdap && $source==UserSourceType::LDAP): ?> - field($user, 'source')->dropDownList(UserSourceType::all(), ['value' => $source]); ?> - field($user, 'email')->widget(Typeahead::class, [ - 'options' => ['placeholder' => Yii::t('mis', 'Filter as you type ...')], - 'pluginOptions' => ['highlight' => true], - 'dataset' => [ - [ - 'display' => 'value', - 'remote' => [ - 'url' => Url::to(['/usuario-ldap/ldap/search']) . '?q=%QUERY', // You can add &limit to set a results limit, 20 to default - 'wildcard' => '%QUERY' - ] + function updateFromLdap(event, data) { + $("#$emailInputId").val(data.value).change(); + } + $('#$sourceId').change(function() { + var source = $(this).val(); + $.pjax.reload({container: '#pjax-user-create', data: {source: source}}) + }) +JS); + +if ($user->isNewRecord) { + if (Yii::$app->getModule('user')->searchUsersInLdap && $source == UserSourceType::LDAP) { + echo $form->field($user, 'source')->dropDownList(UserSourceType::all(), ['value' => $source]); + echo $form->field($user, 'email')->widget(Typeahead::class, [ + 'options' => ['placeholder' => Yii::t('usuario', 'Filter as you type...'), 'autocomplete' => 'off'], + 'pluginOptions' => ['highlight' => true], + 'dataset' => [ + [ + 'display' => 'value', + 'remote' => [ + 'url' => Url::to(['/usuario-ldap/ldap/search']) . '?q=%QUERY', // You can add &limit to set a results limit, 20 to default + 'wildcard' => '%QUERY' ] - ], - // When the email is selected, get the username and change the source from local to ldap - 'pluginEvents' => [ - 'typeahead:select' => 'updateFromLdap', - ], - ]) - ?> - field($user, 'username')->hiddenInput()->label(false) ?> - - field($user, 'source')->dropDownList(UserSourceType::all(), ['value' => $source]); ?> - field($user, 'email')->textInput(['maxlength' => 255]) ?> - field($user, 'username')->textInput(['maxlength' => 255]) ?> - field($user, 'password')->passwordInput() ?> - - - field($user, 'email')->textInput(['maxlength' => 255]) ?> - field($user, 'username')->textInput(['maxlength' => 255]) ?> - field($user, 'password')->passwordInput() ?> - + ] + ], + // When the email is selected, get the username and change the source from local to ldap + 'pluginEvents' => [ + 'typeahead:select' => 'updateFromLdap', + ], + ]); + } else { + echo $form->field($user, 'source')->dropDownList(UserSourceType::all(), ['value' => $source]); + echo $form->field($user, 'email')->textInput(['maxlength' => 255]); + echo $form->field($user, 'username')->textInput(['maxlength' => 255]); + echo $form->field($user, 'password')->passwordInput(); + } +} else { + echo $form->field($user, 'email')->textInput(['maxlength' => 255]); + echo $form->field($user, 'username')->textInput(['maxlength' => 255]); + echo $form->field($user, 'password')->passwordInput(); +} From 1d267556f38c931f67fe094cb1c2e48afd474ded Mon Sep 17 00:00:00 2001 From: "andrea.scaramucci" Date: Fri, 29 Mar 2024 10:32:27 +0100 Subject: [PATCH 3/5] Removed Typeahead and done little improvements --- docs/install/configuration-options.md | 8 ++--- src/User/Bootstrap.php | 2 +- src/User/Controller/AdminController.php | 6 +++- src/User/Model/User.php | 27 +++++++++++++--- src/User/Module.php | 4 +-- src/User/resources/i18n/it/usuario.php | 1 + src/User/resources/views/admin/_user.php | 41 +++++++++++++----------- 7 files changed, 58 insertions(+), 31 deletions(-) diff --git a/docs/install/configuration-options.md b/docs/install/configuration-options.md index 72f61cb9..6764b488 100755 --- a/docs/install/configuration-options.md +++ b/docs/install/configuration-options.md @@ -18,13 +18,13 @@ Number of expired storing records `session history`, values: If this option is `true`, it will be possible to search users in LDAP. -To use this option you need to install kartik typeahead: +To use this option, you need to install [yetopen/yii2-usuario-ldap](https://github.com/YetOpen/yii2-usuario-ldap): ```shell -composer require kartik-v/yii2-widget-typeahead "dev-master" +composer require yetopen/yii2-usuario-ldap "*" ``` -#### ldapUserAttributes (Type: `array`) +#### LDAPUserAttributes (Type: `array`) This array maps the user attributes to sync from ldap @@ -36,7 +36,7 @@ Default to: ] ``` -#### ldapProfileAttributes (Type: `array`) +#### LDAPProfileAttributes (Type: `array`) This array maps the profile attributes to sync from ldap diff --git a/src/User/Bootstrap.php b/src/User/Bootstrap.php index b1f9c336..b8c19411 100755 --- a/src/User/Bootstrap.php +++ b/src/User/Bootstrap.php @@ -156,7 +156,7 @@ function () use ($model) { if (Yii::$app->getModule('user')->searchUsersInLdap) { if (!class_exists('kartik\typeahead\Typeahead')) { - throw new InvalidConfigException('The kartik-v/yii2-widget-typeahead library must be defined when searchUsersInLdap is true.'); + throw new InvalidConfigException('The kartik-v/yii2-widget-typeahead library must be installed when searchUsersInLdap is true.'); } } diff --git a/src/User/Controller/AdminController.php b/src/User/Controller/AdminController.php index 05b1ca2b..e99e60bf 100755 --- a/src/User/Controller/AdminController.php +++ b/src/User/Controller/AdminController.php @@ -143,7 +143,11 @@ public function actionCreate() $this->make(AjaxRequestModelValidator::class, [$user])->validate(); - if ($user->load(Yii::$app->request->post()) && $user->validate()) { + if ($user->load(Yii::$app->request->post())) { + if (!$user->validate()) { + Yii::$app->session->setFlash('danger', implode(', ', $user->getErrorSummary(false))); + return $this->render('create', ['user' => $user]); + } $this->trigger(UserEvent::EVENT_BEFORE_CREATE, $event); $mailService = MailFactory::makeWelcomeMailerService($user); diff --git a/src/User/Model/User.php b/src/User/Model/User.php index 57370b44..e673b160 100644 --- a/src/User/Model/User.php +++ b/src/User/Model/User.php @@ -61,6 +61,7 @@ * @property string $last_login_ip * @property int $password_changed_at * @property int $password_age + * @property int $ldap_uid * Defined relations: * @property SocialNetworkAccount[] $socialNetworkAccounts * @property Profile $profile @@ -74,10 +75,19 @@ class User extends ActiveRecord implements IdentityInterface public const OLD_EMAIL_CONFIRMED = 0b01; public const NEW_EMAIL_CONFIRMED = 0b10; + // ldap error + public const LDAP_INVALID_USER = -1; + /** * @var string Plain password. Used for model validation */ public $password; + + /** + * @var string Stores LDAP uid of the user during creation. + */ + public $ldapUid; + /** * @var array connected account list */ @@ -140,12 +150,16 @@ public static function findIdentityByAccessToken($token, $type = null) */ public function beforeValidate() { - if ($this->module->searchUsersInLdap && $this->source == UserSourceType::LDAP) { + if ($this->module->searchUsersInLdap && $this->source == UserSourceType::LDAP && $this->isNewRecord) { + if ($this->ldapUid == self::LDAP_INVALID_USER) { + $this->addError('ldapUid', Yii::t('usuario', 'Invalid LDAP user')); + return false; + } /** @var UsuarioLdapComponent $ldapComponent */ $ldapComponent = Yii::$app->usuarioLdap; - $this->ldapUser = $ldapComponent->findLdapUser($this->email); + $this->ldapUser = $ldapComponent->findLdapUser($this->ldapUid); if ($this->ldapUser !== null) { - $this->make(InitLdapUserService::class, [$this, $this->module->ldapUserAttributes, $this->ldapUser])->run(); + $this->make(InitLdapUserService::class, [$this, $this->module->LDAPUserAttributes, $this->ldapUser])->run(); } } return parent::beforeValidate(); @@ -188,7 +202,7 @@ public function afterSave($insert, $changedAttributes) if ($insert && $this->profile === null) { $profile = $this->make(Profile::class); if ($this->ldapUser !== null) { - $this->make(InitLdapUserService::class, [$profile, $this->module->ldapProfileAttributes, $this->ldapUser])->run(); + $this->make(InitLdapUserService::class, [$profile, $this->module->LDAPProfileAttributes, $this->ldapUser])->run(); } $profile->link('user', $this); } @@ -238,6 +252,7 @@ public function attributeLabels() 'last_login_ip' => Yii::t('usuario', 'Last login IP'), 'password_changed_at' => Yii::t('usuario', 'Last password change'), 'password_age' => Yii::t('usuario', 'Password age'), + 'ldapUid' => Yii::t('usuario', 'Search'), ]; } @@ -297,6 +312,10 @@ public function rules() 'twoFactorEnabledNumber' => ['auth_tf_enabled', 'boolean'], 'twoFactorTypeLength' => ['auth_tf_type', 'string', 'max' => 20], 'twoFactorMobilePhoneLength' => ['auth_tf_mobile_phone', 'string', 'max' => 20], + + // ldapUid rules + 'ldapUid' => ['ldapUid', 'string'], + 'ldapUidRequired' => ['ldapUid', 'required', 'on' => $this->module->searchUsersInLdap], ]; } diff --git a/src/User/Module.php b/src/User/Module.php index 7dd7a62a..331c21ad 100755 --- a/src/User/Module.php +++ b/src/User/Module.php @@ -39,14 +39,14 @@ class Module extends BaseModule /** * @var array user attributes to sync from ldap */ - public $ldapUserAttributes = [ + public $LDAPUserAttributes = [ 'email' => 'mail', 'username' => 'samaccountname', ]; /** * @var array profile attributes to sync from ldap */ - public $ldapProfileAttributes = []; + public $LDAPProfileAttributes = []; /** * @var int|bool The time after which the expired 'session history' will be deleted * if equals false records will not be deleted diff --git a/src/User/resources/i18n/it/usuario.php b/src/User/resources/i18n/it/usuario.php index 8bcb2892..0b91ce95 100644 --- a/src/User/resources/i18n/it/usuario.php +++ b/src/User/resources/i18n/it/usuario.php @@ -319,4 +319,5 @@ '{0, date, MMMM dd, YYYY HH:mm}' => '{0, date, dd MMMM YYYY HH:mm}', '{0} cannot be blank.' => '{0} non può essere vuoto.', 'Filter as you type...' => 'Digita per filtrare...', + 'Invalid LDAP user' => 'Utente LDAP non valido', ]; diff --git a/src/User/resources/views/admin/_user.php b/src/User/resources/views/admin/_user.php index 84870062..f9b35586 100644 --- a/src/User/resources/views/admin/_user.php +++ b/src/User/resources/views/admin/_user.php @@ -15,17 +15,16 @@ */ use Da\User\Dictionary\UserSourceType; -use kartik\typeahead\Typeahead; +use dosamigos\selectize\SelectizeTextInput; use yii\helpers\Html; use yii\helpers\Url; $source = Yii::$app->request->get('source') ?: $user->source; - -$emailInputId = Html::getInputId($user, 'email'); +$ldapUidId = Html::getInputId($user, 'ldapUid'); $sourceId = Html::getInputId($user, 'source'); $this->registerJs(<<isNewRecord) { if (Yii::$app->getModule('user')->searchUsersInLdap && $source == UserSourceType::LDAP) { echo $form->field($user, 'source')->dropDownList(UserSourceType::all(), ['value' => $source]); - echo $form->field($user, 'email')->widget(Typeahead::class, [ - 'options' => ['placeholder' => Yii::t('usuario', 'Filter as you type...'), 'autocomplete' => 'off'], - 'pluginOptions' => ['highlight' => true], - 'dataset' => [ - [ - 'display' => 'value', - 'remote' => [ - 'url' => Url::to(['/usuario-ldap/ldap/search']) . '?q=%QUERY', // You can add &limit to set a results limit, 20 to default - 'wildcard' => '%QUERY' - ] - ] + echo $form->field($user, 'ldapUid')->widget(SelectizeTextInput::class, [ + 'loadUrl' => Url::to(['/usuario-ldap/ldap/search']), + 'queryParam' => 'q', + 'options' => [ + 'placeholder' => Yii::t('usuario', 'Filter as you type...'), + 'autocomplete' => 'off', ], - // When the email is selected, get the username and change the source from local to ldap - 'pluginEvents' => [ - 'typeahead:select' => 'updateFromLdap', + 'clientOptions' => [ + 'valueField' => 'value', + 'labelField' => 'label', + 'searchField' => ['value', 'label', 'q'], + 'create' => false, + 'maxItems' => 1, + 'onChange' => new \yii\web\JsExpression(" + function(value) { + console.log(value); + updateFromLdap(value); + } + "), ], ]); } else { From e4e78e12785f942ce514553139730705edeee4b0 Mon Sep 17 00:00:00 2001 From: "andrea.scaramucci" Date: Fri, 29 Mar 2024 10:41:27 +0100 Subject: [PATCH 4/5] Translation --- src/User/resources/i18n/it/usuario.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/User/resources/i18n/it/usuario.php b/src/User/resources/i18n/it/usuario.php index 0b91ce95..6cc82f38 100644 --- a/src/User/resources/i18n/it/usuario.php +++ b/src/User/resources/i18n/it/usuario.php @@ -320,4 +320,5 @@ '{0} cannot be blank.' => '{0} non può essere vuoto.', 'Filter as you type...' => 'Digita per filtrare...', 'Invalid LDAP user' => 'Utente LDAP non valido', + 'Search' => 'Cerca', ]; From 128ab8d70167224566ad4fc813580ee6df4ea7bc Mon Sep 17 00:00:00 2001 From: "andrea.scaramucci" Date: Tue, 2 Apr 2024 09:18:26 +0200 Subject: [PATCH 5/5] Fixed params name and description --- docs/install/configuration-options.md | 8 ++++---- src/User/Model/User.php | 4 ++-- src/User/Module.php | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/install/configuration-options.md b/docs/install/configuration-options.md index 6764b488..81c9448a 100755 --- a/docs/install/configuration-options.md +++ b/docs/install/configuration-options.md @@ -24,9 +24,9 @@ To use this option, you need to install [yetopen/yii2-usuario-ldap](https://gith composer require yetopen/yii2-usuario-ldap "*" ``` -#### LDAPUserAttributes (Type: `array`) +#### ldapUserAttributes (Type: `array`) -This array maps the user attributes to sync from ldap +This array maps the user attributes to sync from LDAP Default to: ```php @@ -36,9 +36,9 @@ Default to: ] ``` -#### LDAPProfileAttributes (Type: `array`) +#### ldapProfileAttributes (Type: `array`) -This array maps the profile attributes to sync from ldap +This array maps the profile attributes to sync from LDAP #### timeoutSessionHistory (Type: `boolean, integer`, Default value: `false`) diff --git a/src/User/Model/User.php b/src/User/Model/User.php index e673b160..d3ea817a 100644 --- a/src/User/Model/User.php +++ b/src/User/Model/User.php @@ -159,7 +159,7 @@ public function beforeValidate() $ldapComponent = Yii::$app->usuarioLdap; $this->ldapUser = $ldapComponent->findLdapUser($this->ldapUid); if ($this->ldapUser !== null) { - $this->make(InitLdapUserService::class, [$this, $this->module->LDAPUserAttributes, $this->ldapUser])->run(); + $this->make(InitLdapUserService::class, [$this, $this->module->ldapUserAttributes, $this->ldapUser])->run(); } } return parent::beforeValidate(); @@ -202,7 +202,7 @@ public function afterSave($insert, $changedAttributes) if ($insert && $this->profile === null) { $profile = $this->make(Profile::class); if ($this->ldapUser !== null) { - $this->make(InitLdapUserService::class, [$profile, $this->module->LDAPProfileAttributes, $this->ldapUser])->run(); + $this->make(InitLdapUserService::class, [$profile, $this->module->ldapProfileAttributes, $this->ldapUser])->run(); } $profile->link('user', $this); } diff --git a/src/User/Module.php b/src/User/Module.php index 331c21ad..7dd7a62a 100755 --- a/src/User/Module.php +++ b/src/User/Module.php @@ -39,14 +39,14 @@ class Module extends BaseModule /** * @var array user attributes to sync from ldap */ - public $LDAPUserAttributes = [ + public $ldapUserAttributes = [ 'email' => 'mail', 'username' => 'samaccountname', ]; /** * @var array profile attributes to sync from ldap */ - public $LDAPProfileAttributes = []; + public $ldapProfileAttributes = []; /** * @var int|bool The time after which the expired 'session history' will be deleted * if equals false records will not be deleted