Actualización
This commit is contained in:
40
plugin/azure_active_directory/CHANGELOG.md
Normal file
40
plugin/azure_active_directory/CHANGELOG.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Azure Active Directory Changelog
|
||||
|
||||
## 2.4 - 2024-08-28
|
||||
|
||||
* Added a new user extra field to save the unique Azure ID (internal UID).
|
||||
This requires manually doing the following changes to your database if you are upgrading from v2.3
|
||||
```sql
|
||||
INSERT INTO extra_field (extra_field_type, field_type, variable, display_text, default_value, field_order, visible_to_self, visible_to_others, changeable, filter, created_at) VALUES (1, 1, 'azure_uid', 'Azure UID (internal ID)', '', 1, null, null, null, null, '2024-08-28 00:00:00');
|
||||
```
|
||||
* Added a new option to set the order to verify the existing user in Chamilo
|
||||
```sql
|
||||
INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url, access_url_changeable, access_url_locked) VALUES ('azure_active_directory_existing_user_verification_order', 'azure_active_directory', 'setting', 'Plugins', '', 'azure_active_directory', '', '', '', 1, 1, 0);
|
||||
```
|
||||
* Added a new option to update user info during the login proccess.
|
||||
```sql
|
||||
INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url, access_url_changeable, access_url_locked) VALUES ('azure_active_directory_update_users', 'azure_active_directory', 'setting', 'Plugins', '', 'azure_active_directory', '', '', '', 1, 1, 0);
|
||||
```
|
||||
* Added new scripts to syncronize users and groups with users and usergroups (classes). And an option to deactivate accounts in Chamilo that do not exist in Azure.
|
||||
```sql
|
||||
INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url, access_url_changeable, access_url_locked) VALUES ('azure_active_directory_tenant_id', 'azure_active_directory', 'setting', 'Plugins', '', 'azure_active_directory', '', '', '', 1, 1, 0);
|
||||
INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url, access_url_changeable, access_url_locked) VALUES ('azure_active_directory_deactivate_nonexisting_users', 'azure_active_directory', 'setting', 'Plugins', '', 'azure_active_directory', '', '', '', 1, 1, 0);
|
||||
```
|
||||
|
||||
## 2.3 - 2021-03-30
|
||||
|
||||
* Added admin, session admin and teacher groups. This requires adding the following fields to your database if
|
||||
upgrading from a previous version of the plugin manually:
|
||||
```
|
||||
INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url, access_url_changeable, access_url_locked) VALUES ('azure_active_directory_group_id_admin', 'azure_active_directory', 'setting', 'Plugins', '', 'azure_active_directory', '', null, null, 1, 1, 0);
|
||||
INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url, access_url_changeable, access_url_locked) VALUES ('azure_active_directory_group_id_session_admin', 'azure_active_directory', 'setting', 'Plugins', '', 'azure_active_directory', '', null, null, 1, 1, 0);
|
||||
INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url, access_url_changeable, access_url_locked) VALUES ('azure_active_directory_group_id_teacher', 'azure_active_directory', 'setting', 'Plugins', '', 'azure_active_directory', '', null, null, 1, 1, 0);
|
||||
```
|
||||
|
||||
## 2.2 - 2021-03-02
|
||||
|
||||
* Added provisioning setting
|
||||
|
||||
## 2.1 - 2020
|
||||
|
||||
* Initial tested implementation of Azure Active Directory single sign on
|
||||
46
plugin/azure_active_directory/README.md
Normal file
46
plugin/azure_active_directory/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# The Azure Active Directory Plugin
|
||||
|
||||
This plugin allows users to authenticate (with OAuth2) through Microsoft's Azure Active Directory.
|
||||
This will modify the login form to either substitute the default login form or add another option to connect through
|
||||
Azure.
|
||||
An option allows you to automatically provision/create users in Chamilo from their account on Azure if they don't exist
|
||||
in Chamilo yet.
|
||||
|
||||
This plugin adds two extra fields for users:
|
||||
|
||||
* `organisationemail`, the email registered in Azure Active Directory for each user (under _Email_ in the _Contact info_ section).
|
||||
* `azure_id`, to save the internal ID for each user in Azure (which is also the prefix before the _@_ sign in the _User Principal Name_).
|
||||
|
||||
### Prerequisites
|
||||
This plugin will *not* work if you do not use HTTPS.
|
||||
Make sure your portal is in HTTPS before you configure this plugin.
|
||||
|
||||
### To configure Azure Active Directory
|
||||
* Create and configure an application in your Azure panel (Azure Active Directory -> Applications registration -> New registration)).
|
||||
* In the _Authentication_ section, set an _Reply URL_ (or _Redirect URIs_) of `https://{CHAMILO_URL}/plugin/azure_active_directory/src/callback.php`.
|
||||
* In the _Front-channel logout URL_, use `https://{CHAMILO_URL}/index.php?logout=logout`.
|
||||
* Leave the rest of the _Authentication_ section unchanged.
|
||||
* In _Certificates & secrets_, create a secret string (or application password). Keep the _Value_ field at hand. If you don't copy it somewhere at this point, it will later be hidden, so take a copy, seriously!
|
||||
* Make sure you actually have users.
|
||||
|
||||
### To configure this plugin
|
||||
* _Enable_: You can enable the plugin once everything is configured correctly. Disabling it will return to the normal Chamilo login procedure.
|
||||
* _Application ID_: Enter the _Application (client) ID_ assigned to your app when you created it in your Azure Active Directory interface, under _App registrations_.
|
||||
* _Application secret_: Enter the client secret _value_ created in _Certificate & secrets_ above.
|
||||
* _Block name_: (Optional) The name to show above the login button.
|
||||
* _Force logout button_: (Optional) Add a button to force logout from Azure.
|
||||
* _Management login_: (Optional) Disable the chamilo login and enable an alternative login page for users.
|
||||
You will need copy the `/plugin/azure_active_directory/layout/login_form.tpl` file to `/main/template/overrides/layout/` directory.
|
||||
* _Name for the management login_: A name for the manager login. By default, it is set to "Management Login".
|
||||
* _Automated provisioning_: Enable if you want users to be created automatically in Chamilo (as students) when they don't exist yet.
|
||||
* Assign a region in which the login option will appear. Preferably `login_bottom`.
|
||||
|
||||
### Enable through the normal login form
|
||||
You can configure the external login procedure to work with the classic Chamilo form login.
|
||||
To do it, make sure users have _azure_ in their auth_source field, then add this line in `configuration.php` file
|
||||
```php
|
||||
$extAuthSource["azure"]["login"] = $_configuration['root_sys']."main/auth/external_login/login.azure.php";
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
> This plugin uses the [`thenetworg/oauth2-azure`](https://github.com/TheNetworg/oauth2-azure) package.
|
||||
38
plugin/azure_active_directory/index.php
Normal file
38
plugin/azure_active_directory/index.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.azure_active_directory
|
||||
*/
|
||||
|
||||
// Check if AzureActiveDirectory exists, since this is not loaded as a page.
|
||||
// index.php is shown as a block when showing the region to which the plugin is assigned
|
||||
if (class_exists(AzureActiveDirectory::class)) {
|
||||
/** @var AzureActiveDirectory $activeDirectoryPlugin */
|
||||
$activeDirectoryPlugin = AzureActiveDirectory::create();
|
||||
|
||||
if ($activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_ENABLE) === 'true') {
|
||||
$_template['block_title'] = $activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_BLOCK_NAME);
|
||||
|
||||
$_template['signin_url'] = $activeDirectoryPlugin->getUrl(AzureActiveDirectory::URL_TYPE_AUTHORIZE);
|
||||
|
||||
if ('true' === $activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_FORCE_LOGOUT_BUTTON)) {
|
||||
$_template['signout_url'] = $activeDirectoryPlugin->getUrl(AzureActiveDirectory::URL_TYPE_LOGOUT);
|
||||
}
|
||||
|
||||
$managementLoginEnabled = 'true' === $activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_MANAGEMENT_LOGIN_ENABLE);
|
||||
|
||||
$_template['management_login_enabled'] = $managementLoginEnabled;
|
||||
|
||||
if ($managementLoginEnabled) {
|
||||
$managementLoginName = $activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_MANAGEMENT_LOGIN_NAME);
|
||||
|
||||
if (empty($managementLoginName)) {
|
||||
$managementLoginName = $activeDirectoryPlugin->get_lang('ManagementLogin');
|
||||
}
|
||||
|
||||
$_template['management_login_name'] = $managementLoginName;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
plugin/azure_active_directory/install.php
Normal file
8
plugin/azure_active_directory/install.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
if (!api_is_platform_admin()) {
|
||||
exit('You must have admin permissions to install plugins');
|
||||
}
|
||||
|
||||
AzureActiveDirectory::create()->install();
|
||||
48
plugin/azure_active_directory/lang/dutch.php
Normal file
48
plugin/azure_active_directory/lang/dutch.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Strings to Dutch L10n.
|
||||
*
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.azure_active_directory
|
||||
*/
|
||||
$strings['plugin_title'] = 'Azure Active Directory';
|
||||
$strings['plugin_comment'] = 'Sta authenticatie met Microsoft\'s Azure Active Directory toe';
|
||||
|
||||
$strings['enable'] = 'Inschakelen';
|
||||
$strings['app_id'] = 'Applicatie ID';
|
||||
$strings['app_id_help'] = 'Voeg de Applicatie Id toegewezen aan uw app bij de Azure portal, b.v. 580e250c-8f26-49d0-bee8-1c078add1609';
|
||||
$strings['app_secret'] = 'Applicatie gehem';
|
||||
$strings['force_logout'] = 'Forceer uitlogknop';
|
||||
$strings['force_logout_help'] = 'Toon een knop om afmeldingssessie van Azure af te dwingen.';
|
||||
$strings['block_name'] = 'Blok naam';
|
||||
$strings['management_login_enable'] = 'Beheer login';
|
||||
$strings['management_login_enable_help'] = 'Schakel de chamilo-login uit en schakel een alternatieve inlogpagina in voor gebruikers.<br>'
|
||||
.'U zult moeten kopiëren de <code>/plugin/azure_active_directory/layout/login_form.tpl</code> bestand in het <code>/main/template/overrides/layout/</code> dossier.';
|
||||
$strings['management_login_name'] = 'Naam voor de beheeraanmelding';
|
||||
$strings['management_login_name_help'] = 'De standaardinstelling is "Beheer login".';
|
||||
$strings['existing_user_verification_order'] = 'Existing user verification order';
|
||||
$strings['existing_user_verification_order_help'] = 'This value indicates the order in which the user will be searched in Chamilo to verify its existence. '
|
||||
.'By default is <code>1, 2, 3</code>.'
|
||||
.'<ol><li>EXTRA_FIELD_ORGANISATION_EMAIL (<code>mail</code>)</li><li>EXTRA_FIELD_AZURE_ID (<code>mailNickname</code>)</li><li>EXTRA_FIELD_AZURE_UID (<code>id</code> of <code>objectId</code>)</li></ol>';
|
||||
$strings['OrganisationEmail'] = 'Organisatie e-mail';
|
||||
$strings['AzureId'] = 'Azure ID (mailNickname)';
|
||||
$strings['AzureUid'] = 'Azure UID (internal ID)';
|
||||
$strings['ManagementLogin'] = 'Beheer Login';
|
||||
$strings['InvalidId'] = 'Deze identificatie is niet geldig (verkeerde log-in of wachtwoord). Errocode: AZMNF';
|
||||
$strings['provisioning'] = 'Geautomatiseerde inrichting';
|
||||
$strings['update_users'] = 'Update users';
|
||||
$strings['update_users_help'] = 'Allow user data to be updated at the start of the session.';
|
||||
$strings['provisioning_help'] = 'Maak automatisch nieuwe gebruikers (als studenten) vanuit Azure wanneer ze niet in Chamilo zijn.';
|
||||
$strings['group_id_admin'] = 'Groeps-ID voor platformbeheerders';
|
||||
$strings['group_id_admin_help'] = 'De groeps-ID is te vinden in de details van de gebruikersgroep en ziet er ongeveer zo uit: ae134eef-cbd4-4a32-ba99-49898a1314b6. Indien leeg, wordt er automatisch geen gebruiker aangemaakt als admin.';
|
||||
$strings['group_id_session_admin'] = 'Groeps-ID voor sessiebeheerders';
|
||||
$strings['group_id_session_admin_help'] = 'De groeps-ID voor sessiebeheerders. Indien leeg, wordt er automatisch geen gebruiker aangemaakt als sessiebeheerder.';
|
||||
$strings['group_id_teacher'] = 'Groeps-ID voor docenten';
|
||||
$strings['group_id_teacher_help'] = 'De groeps-ID voor docenten. Indien leeg, wordt er automatisch geen gebruiker aangemaakt als docent.';
|
||||
$strings['additional_interaction_required'] = 'Er is aanvullende interactie vereist om u te authenticeren. Log rechtstreeks in via <a href="https://login.microsoftonline.com" target="_blank">uw authenticatiesysteem</a> en kom dan terug naar deze pagina om in te loggen.';
|
||||
$strings['tenant_id'] = 'Mandanten-ID';
|
||||
$strings['tenant_id_help'] = 'Required to run scripts.';
|
||||
$strings['deactivate_nonexisting_users'] = 'Deactivate non-existing users';
|
||||
$strings['deactivate_nonexisting_users_help'] = 'Compare registered users in Chamilo with those in Azure and deactivate accounts in Chamilo that do not exist in Azure.';
|
||||
48
plugin/azure_active_directory/lang/english.php
Normal file
48
plugin/azure_active_directory/lang/english.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Strings to English L10n.
|
||||
*
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.azure_active_directory
|
||||
*/
|
||||
$strings['plugin_title'] = 'Azure Active Directory';
|
||||
$strings['plugin_comment'] = 'Allow authentication with Microsoft\'s Azure Active Directory';
|
||||
|
||||
$strings['enable'] = 'Enable';
|
||||
$strings['app_id'] = 'Application ID';
|
||||
$strings['app_id_help'] = 'Enter the Application Id assigned to your app by the Azure portal, e.g. 580e250c-8f26-49d0-bee8-1c078add1609';
|
||||
$strings['app_secret'] = 'Application secret';
|
||||
$strings['force_logout'] = 'Force logout button';
|
||||
$strings['force_logout_help'] = 'Show a button to force logout session from Azure.';
|
||||
$strings['block_name'] = 'Block name';
|
||||
$strings['management_login_enable'] = 'Management login';
|
||||
$strings['management_login_enable_help'] = 'Disable the chamilo login and enable an alternative login page for admin users.<br>'
|
||||
.'You will need to copy the <code>/plugin/azure_active_directory/layout/login_form.tpl</code> file to <code>/main/template/overrides/layout/</code> directory.';
|
||||
$strings['management_login_name'] = 'Name for the management login';
|
||||
$strings['management_login_name_help'] = 'The default is "Management Login".';
|
||||
$strings['existing_user_verification_order'] = 'Existing user verification order';
|
||||
$strings['existing_user_verification_order_help'] = 'This value indicates the order in which the user will be searched in Chamilo to verify its existence. '
|
||||
.'By default is <code>1, 2, 3</code>.'
|
||||
.'<ol><li>EXTRA_FIELD_ORGANISATION_EMAIL (<code>mail</code>)</li><li>EXTRA_FIELD_AZURE_ID (<code>mailNickname</code>)</li><li>EXTRA_FIELD_AZURE_UID (<code>id</code> or <code>objectId</code>)</li></ol>';
|
||||
$strings['OrganisationEmail'] = 'Organisation e-mail';
|
||||
$strings['AzureId'] = 'Azure ID (mailNickname)';
|
||||
$strings['AzureUid'] = 'Azure UID (internal ID)';
|
||||
$strings['ManagementLogin'] = 'Management Login';
|
||||
$strings['InvalidId'] = 'Login failed - incorrect login or password. Errocode: AZMNF';
|
||||
$strings['provisioning'] = 'Automated provisioning';
|
||||
$strings['provisioning_help'] = 'Automatically create new users (as students) from Azure when they are not in Chamilo.';
|
||||
$strings['update_users'] = 'Update users';
|
||||
$strings['update_users_help'] = 'Allow user data to be updated at the start of the session.';
|
||||
$strings['group_id_admin'] = 'Group ID for platform admins';
|
||||
$strings['group_id_admin_help'] = 'The group ID can be found in the user group details, looking similar to this: ae134eef-cbd4-4a32-ba99-49898a1314b6. If empty, no user will be automatically created as admin.';
|
||||
$strings['group_id_session_admin'] = 'Group ID for session admins';
|
||||
$strings['group_id_session_admin_help'] = 'The group ID for session admins. If empty, no user will be automatically created as session admin.';
|
||||
$strings['group_id_teacher'] = 'Group ID for teachers';
|
||||
$strings['group_id_teacher_help'] = 'The group ID for teachers. If empty, no user will be automatically created as teacher.';
|
||||
$strings['additional_interaction_required'] = 'Some additional interaction is required to authenticate you. Please login directly through <a href="https://login.microsoftonline.com" target="_blank">your authentication system</a>, then come back to this page to login.';
|
||||
$strings['tenant_id'] = 'Tenant ID';
|
||||
$strings['tenant_id_help'] = 'Required to run scripts.';
|
||||
$strings['deactivate_nonexisting_users'] = 'Deactivate non-existing users';
|
||||
$strings['deactivate_nonexisting_users_help'] = 'Compare registered users in Chamilo with those in Azure and deactivate accounts in Chamilo that do not exist in Azure.';
|
||||
48
plugin/azure_active_directory/lang/french.php
Normal file
48
plugin/azure_active_directory/lang/french.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Strings to French L10n.
|
||||
*
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.azure_active_directory
|
||||
*/
|
||||
$strings['plugin_title'] = 'Azure Active Directory';
|
||||
$strings['plugin_comment'] = 'Permet l\'authentification des utilisateurs via Azure Active Directory de Microsoft';
|
||||
|
||||
$strings['enable'] = 'Activer';
|
||||
$strings['app_id'] = 'ID de l\'application';
|
||||
$strings['app_id_help'] = 'Introduisez l\'ID de l\'application assigné à votre app par le portail d\'Azure, p.ex. 580e250c-8f26-49d0-bee8-1c078add1609';
|
||||
$strings['app_secret'] = 'Clef secrète de l\'application';
|
||||
$strings['force_logout'] = 'Bouton de logout';
|
||||
$strings['force_logout_help'] = 'Affiche un bouton pour se délogger d\'Azure.';
|
||||
$strings['block_name'] = 'Nom du bloc';
|
||||
$strings['management_login_enable'] = 'Login de gestion';
|
||||
$strings['management_login_enable_help'] = 'Désactiver le login de Chamilo et permettre une page de login alternative pour les utilisateurs administrateurs.<br>'
|
||||
.'Vous devez, pour cela, copier le fichier <code>/plugin/azure_active_directory/layout/login_form.tpl</code> dans le répertoire <code>/main/template/overrides/layout/</code>.';
|
||||
$strings['management_login_name'] = 'Nom du login de gestion';
|
||||
$strings['management_login_name_help'] = 'Le nom par défaut est "Login de gestion".';
|
||||
$strings['existing_user_verification_order'] = 'Existing user verification order';
|
||||
$strings['existing_user_verification_order_help'] = 'This value indicates the order in which the user will be searched in Chamilo to verify its existence. '
|
||||
.'By default is <code>1, 2, 3</code>.'
|
||||
.'<ol><li>EXTRA_FIELD_ORGANISATION_EMAIL (<code>mail</code>)</li><li>EXTRA_FIELD_AZURE_ID (<code>mailNickname</code>)</li><li>EXTRA_FIELD_AZURE_UID (<code>id</code> ou <code>objectId</code>)</li></ol>';
|
||||
$strings['OrganisationEmail'] = 'E-mail professionnel';
|
||||
$strings['AzureId'] = 'ID Azure (mailNickname)';
|
||||
$strings['AzureUid'] = 'Azure UID (internal ID)';
|
||||
$strings['ManagementLogin'] = 'Login de gestion';
|
||||
$strings['InvalidId'] = 'Échec du login - nom d\'utilisateur ou mot de passe incorrect. Errocode: AZMNF';
|
||||
$strings['provisioning'] = 'Création automatisée';
|
||||
$strings['provisioning_help'] = 'Créer les utilisateurs automatiquement (en tant qu\'apprenants) depuis Azure s\'ils n\'existent pas encore dans Chamilo.';
|
||||
$strings['update_users'] = 'Actualiser les utilisateurs';
|
||||
$strings['update_users_help'] = 'Permettre d\'actualiser les données de l\'utilisateur lors du démarrage de la session.';
|
||||
$strings['group_id_admin'] = 'ID du groupe administrateur';
|
||||
$strings['group_id_admin_help'] = 'L\'id du groupe peut être trouvé dans les détails du groupe, et ressemble à ceci : ae134eef-cbd4-4a32-ba99-49898a1314b6. Si ce champ est laissé vide, aucun utilisateur ne sera créé en tant qu\'administrateur.';
|
||||
$strings['group_id_session_admin'] = 'ID du groupe administrateur de sessions';
|
||||
$strings['group_id_session_admin_help'] = 'The group ID for session admins. Si ce champ est laissé vide, aucun utilisateur ne sera créé en tant qu\'administrateur de sessions.';
|
||||
$strings['group_id_teacher'] = 'ID du groupe enseignant';
|
||||
$strings['group_id_teacher_help'] = 'The group ID for teachers. Si ce champ est laissé vide, aucun utilisateur ne sera créé en tant qu\'enseignant.';
|
||||
$strings['additional_interaction_required'] = 'Une interaction supplémentaire est nécessaire pour vous authentifier. Veuillez vous connecter directement auprès de <a href="https://login.microsoftonline.com" target="_blank">votre système d\'authentification</a>, puis revenir ici pour vous connecter.';
|
||||
$strings['tenant_id'] = 'ID du client';
|
||||
$strings['tenant_id_help'] = 'Nécessaire pour exécuter des scripts.';
|
||||
$strings['deactivate_nonexisting_users'] = 'Deactivate non-existing users';
|
||||
$strings['deactivate_nonexisting_users_help'] = 'Compare registered users in Chamilo with those in Azure and deactivate accounts in Chamilo that do not exist in Azure.';
|
||||
48
plugin/azure_active_directory/lang/spanish.php
Normal file
48
plugin/azure_active_directory/lang/spanish.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Strings to Spanish L10n.
|
||||
*
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.azure_active_directory
|
||||
*/
|
||||
$strings['plugin_title'] = 'Azure Active Directory';
|
||||
$strings['plugin_comment'] = 'Permite la autenticación de usuarios por Azure Active Directory de Microsoft';
|
||||
|
||||
$strings['enable'] = 'Activar';
|
||||
$strings['app_id'] = 'ID de la aplicación';
|
||||
$strings['app_id_help'] = 'Introduzca el ID de la aplicación asignado a su app en el portal de Azure, p.ej. 580e250c-8f26-49d0-bee8-1c078add1609';
|
||||
$strings['app_secret'] = 'Clave secreta de la aplicación';
|
||||
$strings['force_logout'] = 'Botón de logout';
|
||||
$strings['force_logout_help'] = 'Muestra un botón para hacer logout de Azure.';
|
||||
$strings['block_name'] = 'Nombre del bloque';
|
||||
$strings['management_login_enable'] = 'Login de gestión';
|
||||
$strings['management_login_enable_help'] = 'Desactivar el login de Chamilo y activar una página de login alternativa para los usuarios de administración.<br>'
|
||||
.'Para ello, tendrá que copiar el archivo <code>/plugin/azure_active_directory/layout/login_form.tpl</code> en la carpeta <code>/main/template/overrides/layout/</code>.';
|
||||
$strings['management_login_name'] = 'Nombre del bloque de login de gestión';
|
||||
$strings['management_login_name_help'] = 'El nombre por defecto es "Login de gestión".';
|
||||
$strings['existing_user_verification_order'] = 'Orden de verificación de usuario existente';
|
||||
$strings['existing_user_verification_order_help'] = 'Este valor indica el orden en que el usuario serña buscado en Chamilo para verificar su existencia. '
|
||||
.'Por defecto es <code>1, 2, 3</code>.'
|
||||
.'<ol><li>EXTRA_FIELD_ORGANISATION_EMAIL (<code>mail</code>)</li><li>EXTRA_FIELD_AZURE_ID (<code>mailNickname</code>)</li><li>EXTRA_FIELD_AZURE_UID (<code>id</code> o <code>objectId</code>)</li></ol>';
|
||||
$strings['OrganisationEmail'] = 'E-mail profesional';
|
||||
$strings['AzureId'] = 'ID Azure (mailNickname)';
|
||||
$strings['AzureUid'] = 'UID Azure (ID interno)';
|
||||
$strings['ManagementLogin'] = 'Login de gestión';
|
||||
$strings['InvalidId'] = 'Problema en el login - nombre de usuario o contraseña incorrecto. Errocode: AZMNF';
|
||||
$strings['provisioning'] = 'Creación automatizada';
|
||||
$strings['provisioning_help'] = 'Crear usuarios automáticamente (como alumnos) desde Azure si no existen en Chamilo todavía.';
|
||||
$strings['update_users'] = 'Actualizar los usuarios';
|
||||
$strings['update_users_help'] = 'Permite actualizar los datos del usuario al iniciar sesión.';
|
||||
$strings['group_id_admin'] = 'ID de grupo administrador';
|
||||
$strings['group_id_admin_help'] = 'El ID de grupo se encuentra en los detalles del grupo en Azure, y parece a: ae134eef-cbd4-4a32-ba99-49898a1314b6. Si deja este campo vacío, ningún usuario será creado como administrador.';
|
||||
$strings['group_id_session_admin'] = 'ID de grupo admin de sesiones';
|
||||
$strings['group_id_session_admin_help'] = 'El ID de grupo para administradores de sesiones. Si deja este campo vacío, ningún usuario será creado como administrador de sesiones.';
|
||||
$strings['group_id_teacher'] = 'ID de grupo profesor';
|
||||
$strings['group_id_teacher_help'] = 'El ID de grupo para profesores. Si deja este campo vacío, ningún usuario será creado como profesor.';
|
||||
$strings['additional_interaction_required'] = 'Alguna interacción adicional es necesaria para identificarlo/a. Por favor conéctese primero a través de su <a href="https://login.microsoftonline.com" target="_blank">sistema de autenticación</a>, luego regrese aquí para logearse.';
|
||||
$strings['tenant_id'] = 'Id. del inquilino';
|
||||
$strings['tenant_id_help'] = 'Necesario para ejecutar scripts.';
|
||||
$strings['deactivate_nonexisting_users'] = 'Desactivar usuarios no existentes';
|
||||
$strings['deactivate_nonexisting_users_help'] = 'Compara los usuarios registrados en Chamilo con los de Azure y desactiva las cuentas en Chamilo que no existan en Azure.';
|
||||
51
plugin/azure_active_directory/layout/login_form.tpl
Normal file
51
plugin/azure_active_directory/layout/login_form.tpl
Normal file
@@ -0,0 +1,51 @@
|
||||
{% if _u.logged == 0 %}
|
||||
{% if login_form %}
|
||||
<div id="login-block" class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
{{ login_language_form }}
|
||||
{% if plugin_login_top is not null %}
|
||||
<div id="plugin_login_top">
|
||||
{{ plugin_login_top }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ login_failed }}
|
||||
|
||||
{% set azure_plugin_enabled = 'azure_active_directory'|api_get_plugin_setting('enable') %}
|
||||
{% set azure_plugin_manage_login = 'azure_active_directory'|api_get_plugin_setting('manage_login_enable') %}
|
||||
|
||||
{% if 'false' == azure_plugin_enabled or 'false' == azure_plugin_manage_login %}
|
||||
{{ login_form }}
|
||||
|
||||
{% if "allow_lostpassword" | api_get_setting == 'true' or "allow_registration"|api_get_setting == 'true' %}
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
{% if "allow_registration"|api_get_setting != 'false' %}
|
||||
<li><a href="{{ _p.web_main }}auth/inscription.php"> {{ 'SignUp'|get_lang }} </a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if "allow_lostpassword" | api_get_setting == 'true' %}
|
||||
{% set pass_reminder_link = 'pass_reminder_custom_link'|api_get_configuration_value %}
|
||||
{% set lost_password_link = _p.web_main ~ 'auth/lostPassword.php' %}
|
||||
|
||||
{% if not pass_reminder_link is empty %}
|
||||
{% set lost_password_link = pass_reminder_link %}
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<a href="{{ lost_password_link }}"> {{ 'LostPassword' | get_lang }} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if plugin_login_bottom is not null %}
|
||||
<div id="plugin_login_bottom">
|
||||
{{ plugin_login_bottom }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
35
plugin/azure_active_directory/login.php
Normal file
35
plugin/azure_active_directory/login.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
require __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$plugin = AzureActiveDirectory::create();
|
||||
|
||||
$pluginEnabled = $plugin->get(AzureActiveDirectory::SETTING_ENABLE);
|
||||
$managementLoginEnabled = $plugin->get(AzureActiveDirectory::SETTING_MANAGEMENT_LOGIN_ENABLE);
|
||||
|
||||
if ('true' !== $pluginEnabled || 'true' !== $managementLoginEnabled) {
|
||||
header('Location: '.api_get_path(WEB_PATH));
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = api_get_user_id();
|
||||
|
||||
if (!($userId) || api_is_anonymous($userId)) {
|
||||
$managementLoginName = $plugin->get(AzureActiveDirectory::SETTING_MANAGEMENT_LOGIN_NAME);
|
||||
|
||||
if (empty($managementLoginName)) {
|
||||
$managementLoginName = $plugin->get_lang('ManagementLogin');
|
||||
}
|
||||
|
||||
$template = new Template($managementLoginName);
|
||||
// Only display if the user isn't logged in.
|
||||
$template->assign('login_language_form', api_display_language_form(true, true));
|
||||
$template->assign('login_form', $template->displayLoginForm());
|
||||
|
||||
$content = $template->fetch('azure_active_directory/view/login.tpl');
|
||||
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
}
|
||||
10
plugin/azure_active_directory/plugin.php
Normal file
10
plugin/azure_active_directory/plugin.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.azure_active_directory
|
||||
*/
|
||||
$plugin_info = AzureActiveDirectory::create()->get_info();
|
||||
|
||||
$plugin_info['templates'] = ['view/block.tpl'];
|
||||
388
plugin/azure_active_directory/src/AzureActiveDirectory.php
Normal file
388
plugin/azure_active_directory/src/AzureActiveDirectory.php
Normal file
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
use TheNetworg\OAuth2\Client\Provider\Azure;
|
||||
|
||||
/**
|
||||
* AzureActiveDirectory plugin class.
|
||||
*
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.azure_active_directory
|
||||
*/
|
||||
class AzureActiveDirectory extends Plugin
|
||||
{
|
||||
public const SETTING_ENABLE = 'enable';
|
||||
public const SETTING_APP_ID = 'app_id';
|
||||
public const SETTING_APP_SECRET = 'app_secret';
|
||||
public const SETTING_BLOCK_NAME = 'block_name';
|
||||
public const SETTING_FORCE_LOGOUT_BUTTON = 'force_logout';
|
||||
public const SETTING_MANAGEMENT_LOGIN_ENABLE = 'management_login_enable';
|
||||
public const SETTING_MANAGEMENT_LOGIN_NAME = 'management_login_name';
|
||||
public const SETTING_PROVISION_USERS = 'provisioning';
|
||||
public const SETTING_UPDATE_USERS = 'update_users';
|
||||
public const SETTING_GROUP_ID_ADMIN = 'group_id_admin';
|
||||
public const SETTING_GROUP_ID_SESSION_ADMIN = 'group_id_session_admin';
|
||||
public const SETTING_GROUP_ID_TEACHER = 'group_id_teacher';
|
||||
public const SETTING_EXISTING_USER_VERIFICATION_ORDER = 'existing_user_verification_order';
|
||||
public const SETTING_TENANT_ID = 'tenant_id';
|
||||
public const SETTING_DEACTIVATE_NONEXISTING_USERS = 'deactivate_nonexisting_users';
|
||||
|
||||
public const URL_TYPE_AUTHORIZE = 'login';
|
||||
public const URL_TYPE_LOGOUT = 'logout';
|
||||
|
||||
public const EXTRA_FIELD_ORGANISATION_EMAIL = 'organisationemail';
|
||||
public const EXTRA_FIELD_AZURE_ID = 'azure_id';
|
||||
public const EXTRA_FIELD_AZURE_UID = 'azure_uid';
|
||||
|
||||
public const API_PAGE_SIZE = 999;
|
||||
|
||||
/**
|
||||
* AzureActiveDirectory constructor.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
$settings = [
|
||||
self::SETTING_ENABLE => 'boolean',
|
||||
self::SETTING_APP_ID => 'text',
|
||||
self::SETTING_APP_SECRET => 'text',
|
||||
self::SETTING_BLOCK_NAME => 'text',
|
||||
self::SETTING_FORCE_LOGOUT_BUTTON => 'boolean',
|
||||
self::SETTING_MANAGEMENT_LOGIN_ENABLE => 'boolean',
|
||||
self::SETTING_MANAGEMENT_LOGIN_NAME => 'text',
|
||||
self::SETTING_PROVISION_USERS => 'boolean',
|
||||
self::SETTING_UPDATE_USERS => 'boolean',
|
||||
self::SETTING_GROUP_ID_ADMIN => 'text',
|
||||
self::SETTING_GROUP_ID_SESSION_ADMIN => 'text',
|
||||
self::SETTING_GROUP_ID_TEACHER => 'text',
|
||||
self::SETTING_EXISTING_USER_VERIFICATION_ORDER => 'text',
|
||||
self::SETTING_TENANT_ID => 'text',
|
||||
self::SETTING_DEACTIVATE_NONEXISTING_USERS => 'boolean',
|
||||
];
|
||||
|
||||
parent::__construct('2.4', 'Angel Fernando Quiroz Campos, Yannick Warnier', $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance the plugin.
|
||||
*
|
||||
* @staticvar null $result
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
static $result = null;
|
||||
|
||||
return $result ? $result : $result = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name()
|
||||
{
|
||||
return 'azure_active_directory';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Azure
|
||||
*/
|
||||
public function getProvider()
|
||||
{
|
||||
$provider = new Azure([
|
||||
'clientId' => $this->get(self::SETTING_APP_ID),
|
||||
'clientSecret' => $this->get(self::SETTING_APP_SECRET),
|
||||
'redirectUri' => api_get_path(WEB_PLUGIN_PATH).'azure_active_directory/src/callback.php',
|
||||
]);
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
public function getProviderForApiGraph(): Azure
|
||||
{
|
||||
$provider = $this->getProvider();
|
||||
$provider->urlAPI = "https://graph.microsoft.com/v1.0/";
|
||||
$provider->resource = "https://graph.microsoft.com/";
|
||||
$provider->tenant = $this->get(AzureActiveDirectory::SETTING_TENANT_ID);
|
||||
$provider->authWithResource = false;
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $urlType Type of URL to generate
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl($urlType)
|
||||
{
|
||||
if (self::URL_TYPE_LOGOUT === $urlType) {
|
||||
$provider = $this->getProvider();
|
||||
|
||||
return $provider->getLogoutUrl(
|
||||
api_get_path(WEB_PATH)
|
||||
);
|
||||
}
|
||||
|
||||
return api_get_path(WEB_PLUGIN_PATH).$this->get_name().'/src/callback.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create extra fields for user when installing.
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
UserManager::create_extra_field(
|
||||
self::EXTRA_FIELD_ORGANISATION_EMAIL,
|
||||
ExtraField::FIELD_TYPE_TEXT,
|
||||
$this->get_lang('OrganisationEmail'),
|
||||
''
|
||||
);
|
||||
UserManager::create_extra_field(
|
||||
self::EXTRA_FIELD_AZURE_ID,
|
||||
ExtraField::FIELD_TYPE_TEXT,
|
||||
$this->get_lang('AzureId'),
|
||||
''
|
||||
);
|
||||
UserManager::create_extra_field(
|
||||
self::EXTRA_FIELD_AZURE_UID,
|
||||
ExtraField::FIELD_TYPE_TEXT,
|
||||
$this->get_lang('AzureUid'),
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
public function getExistingUserVerificationOrder(): array
|
||||
{
|
||||
$defaultOrder = [1, 2, 3];
|
||||
|
||||
$settingValue = $this->get(self::SETTING_EXISTING_USER_VERIFICATION_ORDER);
|
||||
$selectedOrder = array_filter(
|
||||
array_map(
|
||||
'trim',
|
||||
explode(',', $settingValue)
|
||||
)
|
||||
);
|
||||
$selectedOrder = array_map('intval', $selectedOrder);
|
||||
$selectedOrder = array_filter(
|
||||
$selectedOrder,
|
||||
function ($position) use ($defaultOrder): bool {
|
||||
return in_array($position, $defaultOrder);
|
||||
}
|
||||
);
|
||||
|
||||
if ($selectedOrder) {
|
||||
return $selectedOrder;
|
||||
}
|
||||
|
||||
return $defaultOrder;
|
||||
}
|
||||
|
||||
public function getUserIdByVerificationOrder(array $azureUserData, string $azureUidKey = 'objectId'): ?int
|
||||
{
|
||||
$selectedOrder = $this->getExistingUserVerificationOrder();
|
||||
|
||||
$extraFieldValue = new ExtraFieldValue('user');
|
||||
$positionsAndFields = [
|
||||
1 => $extraFieldValue->get_item_id_from_field_variable_and_field_value(
|
||||
AzureActiveDirectory::EXTRA_FIELD_ORGANISATION_EMAIL,
|
||||
$azureUserData['mail']
|
||||
),
|
||||
2 => $extraFieldValue->get_item_id_from_field_variable_and_field_value(
|
||||
AzureActiveDirectory::EXTRA_FIELD_AZURE_ID,
|
||||
$azureUserData['mailNickname']
|
||||
),
|
||||
3 => $extraFieldValue->get_item_id_from_field_variable_and_field_value(
|
||||
AzureActiveDirectory::EXTRA_FIELD_AZURE_UID,
|
||||
$azureUserData[$azureUidKey]
|
||||
),
|
||||
];
|
||||
|
||||
foreach ($selectedOrder as $position) {
|
||||
if (!empty($positionsAndFields[$position]) && isset($positionsAndFields[$position]['item_id'])) {
|
||||
return (int) $positionsAndFields[$position]['item_id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function registerUser(
|
||||
array $azureUserInfo,
|
||||
string $azureUidKey = 'objectId'
|
||||
) {
|
||||
if (empty($azureUserInfo)) {
|
||||
throw new Exception('Groups info not found.');
|
||||
}
|
||||
|
||||
$userId = $this->getUserIdByVerificationOrder($azureUserInfo, $azureUidKey);
|
||||
|
||||
if (empty($userId)) {
|
||||
// If we didn't find the user
|
||||
if ($this->get(self::SETTING_PROVISION_USERS) !== 'true') {
|
||||
throw new Exception('User not found when checking the extra fields from '.$azureUserInfo['mail'].' or '.$azureUserInfo['mailNickname'].' or '.$azureUserInfo[$azureUidKey].'.');
|
||||
}
|
||||
|
||||
[
|
||||
$firstNme,
|
||||
$lastName,
|
||||
$username,
|
||||
$email,
|
||||
$phone,
|
||||
$authSource,
|
||||
$active,
|
||||
$extra,
|
||||
] = $this->formatUserData($azureUserInfo, $azureUidKey);
|
||||
|
||||
// If the option is set to create users, create it
|
||||
$userId = UserManager::create_user(
|
||||
$firstNme,
|
||||
$lastName,
|
||||
STUDENT,
|
||||
$email,
|
||||
$username,
|
||||
'',
|
||||
null,
|
||||
null,
|
||||
$phone,
|
||||
null,
|
||||
$authSource,
|
||||
null,
|
||||
$active,
|
||||
null,
|
||||
$extra,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
if (!$userId) {
|
||||
throw new Exception(get_lang('UserNotAdded').' '.$azureUserInfo['userPrincipalName']);
|
||||
}
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
if ($this->get(self::SETTING_UPDATE_USERS) === 'true') {
|
||||
[
|
||||
$firstNme,
|
||||
$lastName,
|
||||
$username,
|
||||
$email,
|
||||
$phone,
|
||||
$authSource,
|
||||
$active,
|
||||
$extra,
|
||||
] = $this->formatUserData($azureUserInfo, $azureUidKey);
|
||||
|
||||
$userId = UserManager::update_user(
|
||||
$userId,
|
||||
$firstNme,
|
||||
$lastName,
|
||||
$username,
|
||||
'',
|
||||
$authSource,
|
||||
$email,
|
||||
STUDENT,
|
||||
null,
|
||||
$phone,
|
||||
null,
|
||||
null,
|
||||
$active,
|
||||
null,
|
||||
0,
|
||||
$extra
|
||||
);
|
||||
|
||||
if (!$userId) {
|
||||
throw new Exception(get_lang('CouldNotUpdateUser').' '.$azureUserInfo['userPrincipalName']);
|
||||
}
|
||||
}
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|false>
|
||||
*/
|
||||
public function getGroupUidByRole(): array
|
||||
{
|
||||
$groupUidList = [
|
||||
'admin' => $this->get(self::SETTING_GROUP_ID_ADMIN),
|
||||
'sessionAdmin' => $this->get(self::SETTING_GROUP_ID_SESSION_ADMIN),
|
||||
'teacher' => $this->get(self::SETTING_GROUP_ID_TEACHER),
|
||||
];
|
||||
|
||||
return array_filter($groupUidList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, callable>
|
||||
*/
|
||||
public function getUpdateActionByRole(): array
|
||||
{
|
||||
return [
|
||||
'admin' => function (User $user) {
|
||||
$user->setStatus(COURSEMANAGER);
|
||||
|
||||
UserManager::addUserAsAdmin($user, false);
|
||||
},
|
||||
'sessionAdmin' => function (User $user) {
|
||||
$user->setStatus(SESSIONADMIN);
|
||||
|
||||
UserManager::removeUserAdmin($user, false);
|
||||
},
|
||||
'teacher' => function (User $user) {
|
||||
$user->setStatus(COURSEMANAGER);
|
||||
|
||||
UserManager::removeUserAdmin($user, false);
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function formatUserData(
|
||||
array $azureUserInfo,
|
||||
string $azureUidKey
|
||||
): array {
|
||||
$phone = null;
|
||||
|
||||
if (isset($azureUserInfo['telephoneNumber'])) {
|
||||
$phone = $azureUserInfo['telephoneNumber'];
|
||||
} elseif (isset($azureUserInfo['businessPhones'][0])) {
|
||||
$phone = $azureUserInfo['businessPhones'][0];
|
||||
} elseif (isset($azureUserInfo['mobilePhone'])) {
|
||||
$phone = $azureUserInfo['mobilePhone'];
|
||||
}
|
||||
|
||||
// If the option is set to create users, create it
|
||||
$firstNme = $azureUserInfo['givenName'];
|
||||
$lastName = $azureUserInfo['surname'];
|
||||
$email = $azureUserInfo['mail'];
|
||||
$username = $azureUserInfo['userPrincipalName'];
|
||||
$authSource = 'azure';
|
||||
$active = ($azureUserInfo['accountEnabled'] ? 1 : 0);
|
||||
$extra = [
|
||||
'extra_'.self::EXTRA_FIELD_ORGANISATION_EMAIL => $azureUserInfo['mail'],
|
||||
'extra_'.self::EXTRA_FIELD_AZURE_ID => $azureUserInfo['mailNickname'],
|
||||
'extra_'.self::EXTRA_FIELD_AZURE_UID => $azureUserInfo[$azureUidKey],
|
||||
];
|
||||
|
||||
return [
|
||||
$firstNme,
|
||||
$lastName,
|
||||
$username,
|
||||
$email,
|
||||
$phone,
|
||||
$authSource,
|
||||
$active,
|
||||
$extra,
|
||||
];
|
||||
}
|
||||
}
|
||||
188
plugin/azure_active_directory/src/AzureCommand.php
Normal file
188
plugin/azure_active_directory/src/AzureCommand.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||
use League\OAuth2\Client\Token\AccessTokenInterface;
|
||||
use TheNetworg\OAuth2\Client\Provider\Azure;
|
||||
|
||||
abstract class AzureCommand
|
||||
{
|
||||
/**
|
||||
* @var AzureActiveDirectory
|
||||
*/
|
||||
protected $plugin;
|
||||
/**
|
||||
* @var Azure
|
||||
*/
|
||||
protected $provider;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->plugin = AzureActiveDirectory::create();
|
||||
$this->plugin->get_settings(true);
|
||||
$this->provider = $this->plugin->getProviderForApiGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IdentityProviderException
|
||||
*/
|
||||
protected function generateOrRefreshToken(?AccessTokenInterface &$token)
|
||||
{
|
||||
if (!$token || ($token->getExpires() && !$token->getRefreshToken())) {
|
||||
$token = $this->provider->getAccessToken(
|
||||
'client_credentials',
|
||||
['resource' => $this->provider->resource]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Generator<int, array<string, string>>
|
||||
*/
|
||||
protected function getAzureUsers(): Generator
|
||||
{
|
||||
$userFields = [
|
||||
'givenName',
|
||||
'surname',
|
||||
'mail',
|
||||
'userPrincipalName',
|
||||
'businessPhones',
|
||||
'mobilePhone',
|
||||
'accountEnabled',
|
||||
'mailNickname',
|
||||
'id',
|
||||
];
|
||||
|
||||
$query = sprintf(
|
||||
'$top=%d&$select=%s',
|
||||
AzureActiveDirectory::API_PAGE_SIZE,
|
||||
implode(',', $userFields)
|
||||
);
|
||||
|
||||
$token = null;
|
||||
|
||||
do {
|
||||
$this->generateOrRefreshToken($token);
|
||||
|
||||
try {
|
||||
$azureUsersRequest = $this->provider->request(
|
||||
'get',
|
||||
"users?$query",
|
||||
$token
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Exception when requesting users from Azure: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$azureUsersInfo = $azureUsersRequest['value'] ?? [];
|
||||
|
||||
foreach ($azureUsersInfo as $azureUserInfo) {
|
||||
yield $azureUserInfo;
|
||||
}
|
||||
|
||||
$hasNextLink = false;
|
||||
|
||||
if (!empty($azureUsersRequest['@odata.nextLink'])) {
|
||||
$hasNextLink = true;
|
||||
$query = parse_url($azureUsersRequest['@odata.nextLink'], PHP_URL_QUERY);
|
||||
}
|
||||
} while ($hasNextLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Generator<int, array<string, string>>
|
||||
*/
|
||||
protected function getAzureGroups(): Generator
|
||||
{
|
||||
$groupFields = [
|
||||
'id',
|
||||
'displayName',
|
||||
'description',
|
||||
];
|
||||
|
||||
$query = sprintf(
|
||||
'$top=%d&$select=%s',
|
||||
AzureActiveDirectory::API_PAGE_SIZE,
|
||||
implode(',', $groupFields)
|
||||
);
|
||||
|
||||
$token = null;
|
||||
|
||||
do {
|
||||
$this->generateOrRefreshToken($token);
|
||||
|
||||
try {
|
||||
$azureGroupsRequest = $this->provider->request('get', "groups?$query", $token);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Exception when requesting groups from Azure: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$azureGroupsInfo = $azureGroupsRequest['value'] ?? [];
|
||||
|
||||
foreach ($azureGroupsInfo as $azureGroupInfo) {
|
||||
yield $azureGroupInfo;
|
||||
}
|
||||
|
||||
$hasNextLink = false;
|
||||
|
||||
if (!empty($azureGroupsRequest['@odata.nextLink'])) {
|
||||
$hasNextLink = true;
|
||||
$query = parse_url($azureGroupsRequest['@odata.nextLink'], PHP_URL_QUERY);
|
||||
}
|
||||
} while ($hasNextLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Generator<int, array<string, string>>
|
||||
*/
|
||||
protected function getAzureGroupMembers(string $groupUid): Generator
|
||||
{
|
||||
$userFields = [
|
||||
'mail',
|
||||
'mailNickname',
|
||||
'id',
|
||||
];
|
||||
|
||||
$query = sprintf(
|
||||
'$top=%d&$select=%s',
|
||||
AzureActiveDirectory::API_PAGE_SIZE,
|
||||
implode(',', $userFields)
|
||||
);
|
||||
|
||||
$token = null;
|
||||
|
||||
do {
|
||||
$this->generateOrRefreshToken($token);
|
||||
|
||||
try {
|
||||
$azureGroupMembersRequest = $this->provider->request(
|
||||
'get',
|
||||
"groups/$groupUid/members?$query",
|
||||
$token
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Exception when requesting group members from Azure: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$azureGroupMembers = $azureGroupMembersRequest['value'] ?? [];
|
||||
|
||||
foreach ($azureGroupMembers as $azureGroupMember) {
|
||||
yield $azureGroupMember;
|
||||
}
|
||||
|
||||
$hasNextLink = false;
|
||||
|
||||
if (!empty($azureGroupMembersRequest['@odata.nextLink'])) {
|
||||
$hasNextLink = true;
|
||||
$query = parse_url($azureGroupMembersRequest['@odata.nextLink'], PHP_URL_QUERY);
|
||||
}
|
||||
} while ($hasNextLink);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
class AzureSyncUsergroupsCommand extends AzureCommand
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Generator<int, string>
|
||||
*/
|
||||
public function __invoke(): Generator
|
||||
{
|
||||
yield 'Synchronizing groups from Azure.';
|
||||
|
||||
$usergroup = new UserGroup();
|
||||
|
||||
$groupIdByUid = [];
|
||||
|
||||
foreach ($this->getAzureGroups() as $azureGroupInfo) {
|
||||
if ($usergroup->usergroup_exists($azureGroupInfo['displayName'])) {
|
||||
$groupId = $usergroup->getIdByName($azureGroupInfo['displayName']);
|
||||
|
||||
if ($groupId) {
|
||||
$usergroup->subscribe_users_to_usergroup($groupId, []);
|
||||
|
||||
yield sprintf('Class exists, all users unsubscribed: %s', $azureGroupInfo['displayName']);
|
||||
}
|
||||
} else {
|
||||
$groupId = $usergroup->save([
|
||||
'name' => $azureGroupInfo['displayName'],
|
||||
'description' => $azureGroupInfo['description'],
|
||||
]);
|
||||
|
||||
if ($groupId) {
|
||||
yield sprintf('Class created: %s', $azureGroupInfo['displayName']);
|
||||
}
|
||||
}
|
||||
|
||||
$groupIdByUid[$azureGroupInfo['id']] = $groupId;
|
||||
}
|
||||
|
||||
yield '----------------';
|
||||
yield 'Subscribing users to groups';
|
||||
|
||||
foreach ($groupIdByUid as $azureGroupUid => $groupId) {
|
||||
$newGroupMembers = [];
|
||||
|
||||
yield sprintf('Obtaining members for group (ID %d)', $groupId);
|
||||
|
||||
try {
|
||||
foreach ($this->getAzureGroupMembers($azureGroupUid) as $azureGroupMember) {
|
||||
if ($userId = $this->plugin->getUserIdByVerificationOrder($azureGroupMember, 'id')) {
|
||||
$newGroupMembers[] = $userId;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
yield $e->getMessage();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($newGroupMembers) {
|
||||
$usergroup->subscribe_users_to_usergroup($groupId, $newGroupMembers);
|
||||
|
||||
yield sprintf(
|
||||
'User IDs subscribed in class (ID %d): %s',
|
||||
$groupId,
|
||||
implode(', ', $newGroupMembers)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
plugin/azure_active_directory/src/AzureSyncUsersCommand.php
Normal file
98
plugin/azure_active_directory/src/AzureSyncUsersCommand.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
|
||||
class AzureSyncUsersCommand extends AzureCommand
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Generator<int, string>
|
||||
*/
|
||||
public function __invoke(): Generator
|
||||
{
|
||||
yield 'Synchronizing users from Azure.';
|
||||
|
||||
/** @var array<string, int> $existingUsers */
|
||||
$existingUsers = [];
|
||||
|
||||
foreach ($this->getAzureUsers() as $azureUserInfo) {
|
||||
try {
|
||||
$userId = $this->plugin->registerUser($azureUserInfo, 'id');
|
||||
} catch (Exception $e) {
|
||||
yield $e->getMessage();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$existingUsers[$azureUserInfo['id']] = $userId;
|
||||
|
||||
yield sprintf('User (ID %d) with received info: %s ', $userId, serialize($azureUserInfo));
|
||||
}
|
||||
|
||||
yield '----------------';
|
||||
yield 'Updating users status';
|
||||
|
||||
$roleGroups = $this->plugin->getGroupUidByRole();
|
||||
$roleActions = $this->plugin->getUpdateActionByRole();
|
||||
|
||||
$userManager = UserManager::getManager();
|
||||
$em = Database::getManager();
|
||||
|
||||
foreach ($roleGroups as $userRole => $groupUid) {
|
||||
try {
|
||||
$azureGroupMembersInfo = iterator_to_array($this->getAzureGroupMembers($groupUid));
|
||||
} catch (Exception $e) {
|
||||
yield $e->getMessage();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$azureGroupMembersUids = array_column($azureGroupMembersInfo, 'id');
|
||||
|
||||
foreach ($azureGroupMembersUids as $azureGroupMembersUid) {
|
||||
$userId = $existingUsers[$azureGroupMembersUid] ?? null;
|
||||
|
||||
if (!$userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($roleActions[$userRole])) {
|
||||
/** @var User $user */
|
||||
$user = $userManager->find($userId);
|
||||
|
||||
$roleActions[$userRole]($user);
|
||||
|
||||
yield sprintf('User (ID %d) status %s', $userId, $userRole);
|
||||
}
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
if ('true' === $this->plugin->get(AzureActiveDirectory::SETTING_DEACTIVATE_NONEXISTING_USERS)) {
|
||||
yield '----------------';
|
||||
|
||||
yield 'Trying deactivate non-existing users in Azure';
|
||||
|
||||
$users = UserManager::getRepository()->findByAuthSource('azure');
|
||||
$userIdList = array_map(
|
||||
function ($user) {
|
||||
return $user->getId();
|
||||
},
|
||||
$users
|
||||
);
|
||||
|
||||
$nonExistingUsers = array_diff($userIdList, $existingUsers);
|
||||
|
||||
UserManager::deactivate_users($nonExistingUsers);
|
||||
|
||||
yield sprintf(
|
||||
'Deactivated users IDs: %s',
|
||||
implode(', ', $nonExistingUsers)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
plugin/azure_active_directory/src/callback.php
Normal file
145
plugin/azure_active_directory/src/callback.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
/**
|
||||
* Callback script for Azure. The URL of this file is sent to Azure as a
|
||||
* point of contact to send particular signals.
|
||||
*/
|
||||
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
|
||||
require __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
if (!empty($_GET['error']) && !empty($_GET['state'])) {
|
||||
if ($_GET['state'] === ChamiloSession::read('oauth2state')) {
|
||||
api_not_allowed(
|
||||
true,
|
||||
Display::return_message(
|
||||
$_GET['error_description'] ?? $_GET['error'],
|
||||
'warning'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
ChamiloSession::erase('oauth2state');
|
||||
exit('Invalid state');
|
||||
}
|
||||
}
|
||||
|
||||
$plugin = AzureActiveDirectory::create();
|
||||
|
||||
if ('true' !== $plugin->get(AzureActiveDirectory::SETTING_ENABLE)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$provider = $plugin->getProvider();
|
||||
|
||||
if (!isset($_GET['code'])) {
|
||||
// If we don't have an authorization code then get one by redirecting
|
||||
// users to Azure (with the callback URL information)
|
||||
$authUrl = $provider->getAuthorizationUrl();
|
||||
|
||||
ChamiloSession::write('oauth2state', $provider->getState());
|
||||
|
||||
header('Location: '.$authUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check given state against previously stored one to mitigate CSRF attack
|
||||
if (empty($_GET['state']) || ($_GET['state'] !== ChamiloSession::read('oauth2state'))) {
|
||||
ChamiloSession::erase('oauth2state');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// Try to get an access token (using the authorization code grant)
|
||||
try {
|
||||
$token = $provider->getAccessToken('authorization_code', [
|
||||
'code' => $_GET['code'],
|
||||
'resource' => 'https://graph.windows.net',
|
||||
]);
|
||||
} catch (Exception $exception) {
|
||||
if ($exception->getMessage() == 'interaction_required') {
|
||||
$message = Display::return_message($plugin->get_lang('additional_interaction_required'), 'error', false);
|
||||
} else {
|
||||
$message = Display::return_message($exception->getMessage(), 'error');
|
||||
}
|
||||
Display::addFlash($message);
|
||||
header('Location: '.api_get_path(WEB_PATH));
|
||||
exit;
|
||||
}
|
||||
|
||||
$me = null;
|
||||
|
||||
try {
|
||||
$me = $provider->get('me', $token);
|
||||
|
||||
if (empty($me)) {
|
||||
throw new Exception('Token not found.');
|
||||
}
|
||||
|
||||
// We use the e-mail to authenticate the user, so check that at least one
|
||||
// e-mail source exists
|
||||
if (empty($me['mail'])) {
|
||||
throw new Exception('The mail field is empty in Azure AD and is needed to set the organisation email for this user.');
|
||||
}
|
||||
if (empty($me['mailNickname'])) {
|
||||
throw new Exception('The mailNickname field is empty in Azure AD and is needed to set the unique username for this user.');
|
||||
}
|
||||
if (empty($me['objectId'])) {
|
||||
throw new Exception('The id field is empty in Azure AD and is needed to set the unique Azure ID for this user.');
|
||||
}
|
||||
|
||||
$userId = $plugin->registerUser($me);
|
||||
|
||||
if ($roleGroups = $plugin->getGroupUidByRole()) {
|
||||
$roleActions = $plugin->getUpdateActionByRole();
|
||||
/** @var User $user */
|
||||
$user = UserManager::getManager()->find($userId);
|
||||
|
||||
$azureGroups = $provider->get('me/memberOf', $token);
|
||||
|
||||
foreach ($roleGroups as $userRole => $groupUid) {
|
||||
foreach ($azureGroups as $azureGroup) {
|
||||
$azureGroupUid = $azureGroup['objectId'];
|
||||
if ($azureGroupUid === $groupUid) {
|
||||
$roleActions[$userRole]($user);
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Database::getManager()->flush();
|
||||
}
|
||||
|
||||
$userInfo = api_get_user_info($userId);
|
||||
|
||||
/* @TODO add support if user exists in another URL but is validated in this one, add the user to access_url_rel_user */
|
||||
|
||||
if (empty($userInfo)) {
|
||||
throw new Exception('User '.$userId.' not found.');
|
||||
}
|
||||
|
||||
if ($userInfo['active'] != '1') {
|
||||
throw new Exception(get_lang('AccountInactive'));
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
$message = Display::return_message($exception->getMessage(), 'error');
|
||||
Display::addFlash($message);
|
||||
header('Location: '.api_get_path(WEB_PATH));
|
||||
exit;
|
||||
}
|
||||
|
||||
$userInfo['uidReset'] = true;
|
||||
|
||||
$_GET['redirect_after_not_allow_page'] = 1;
|
||||
|
||||
$redirectAfterNotAllowPage = ChamiloSession::read('redirect_after_not_allow_page');
|
||||
|
||||
ChamiloSession::clear();
|
||||
|
||||
ChamiloSession::write('redirect_after_not_allow_page', $redirectAfterNotAllowPage);
|
||||
|
||||
ChamiloSession::write('_user', $userInfo);
|
||||
ChamiloSession::write('_user_auth_source', 'azure_active_directory');
|
||||
Event::eventLogin($userInfo['user_id']);
|
||||
Redirect::session_request_uri(true, $userInfo['user_id']);
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
require __DIR__.'/../../../../main/inc/global.inc.php';
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
exit('Run this script through the command line or comment this line in the code');
|
||||
}
|
||||
|
||||
// Uncomment to indicate the access url to get the plugin settings when using multi-url
|
||||
//$_configuration['access_url'] = 1;
|
||||
|
||||
$command = new AzureSyncUsergroupsCommand();
|
||||
|
||||
try {
|
||||
foreach ($command() as $str) {
|
||||
printf("%d - %s".PHP_EOL, time(), $str);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
printf('%s - Exception: %s'.PHP_EOL, time(), $e->getMessage());
|
||||
}
|
||||
21
plugin/azure_active_directory/src/scripts/sync_users.php
Normal file
21
plugin/azure_active_directory/src/scripts/sync_users.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
require __DIR__.'/../../../../main/inc/global.inc.php';
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
exit('Run this script through the command line or comment this line in the code');
|
||||
}
|
||||
|
||||
// Uncomment to indicate the access url to get the plugin settings when using multi-url
|
||||
//$_configuration['access_url'] = 1;
|
||||
|
||||
$command = new AzureSyncUsersCommand();
|
||||
|
||||
try {
|
||||
foreach ($command() as $str) {
|
||||
printf("%d - %s".PHP_EOL, time(), $str);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
printf('%s - Exception: %s'.PHP_EOL, time(), $e->getMessage());
|
||||
}
|
||||
22
plugin/azure_active_directory/view/block.tpl
Normal file
22
plugin/azure_active_directory/view/block.tpl
Normal file
@@ -0,0 +1,22 @@
|
||||
{% if not _u.logged %}
|
||||
<div id="azure-active-directory-login">
|
||||
{% if not azure_active_directory.block_title is empty %}
|
||||
<h4>{{ azure_active_directory.block_title }}</h4>
|
||||
{% endif %}
|
||||
|
||||
{% if not azure_active_directory.signin_url is empty %}
|
||||
<a href="{{ azure_active_directory.signin_url }}" class="btn btn-default">{{ 'SignIn'|get_lang }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if not azure_active_directory.signout_url is empty %}
|
||||
<a href="{{ azure_active_directory.signout_url }}" class="btn btn-danger">{{ 'Logout'|get_lang }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if azure_active_directory.management_login_enabled %}
|
||||
<hr>
|
||||
<a href="{{ _p.web_plugin ~ 'azure_active_directory/login.php' }}">
|
||||
{{ azure_active_directory.management_login_name }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
26
plugin/azure_active_directory/view/login.tpl
Normal file
26
plugin/azure_active_directory/view/login.tpl
Normal file
@@ -0,0 +1,26 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-sm-offset-4">
|
||||
{{ login_language_form }}
|
||||
|
||||
{{ login_form }}
|
||||
|
||||
{% if "allow_lostpassword"|api_get_setting == 'true' or "allow_registration"|api_get_setting == 'true' %}
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
{% if "allow_registration"|api_get_setting != 'false' %}
|
||||
<li><a href="{{ _p.web_main }}auth/inscription.php">{{ 'SignUp'|get_lang }}</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if "allow_lostpassword" | api_get_setting == 'true' %}
|
||||
{% set pass_reminder_link = 'pass_reminder_custom_link'|api_get_configuration_value %}
|
||||
{% set lost_password_link = _p.web_main ~ 'auth/lostPassword.php' %}
|
||||
|
||||
{% if not pass_reminder_link is empty %}
|
||||
{% set lost_password_link = pass_reminder_link %}
|
||||
{% endif %}
|
||||
|
||||
<li><a href="{{ lost_password_link }}"> {{ 'LostPassword' | get_lang }} </a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user