upgrade
This commit is contained in:
22
plugin/oauth2/README.md
Normal file
22
plugin/oauth2/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# The OAuth2 Plugin
|
||||
Allows authentication with a generic OAuth2 provider.
|
||||
|
||||
This plugin adds an extra field to users :
|
||||
- `oauth2_id`, to store each users' OAuth2 identifier.
|
||||
|
||||
> This plugin uses the [`league/oauth2-client`](https://oauth2-client.thephpleague.com/) package.
|
||||
|
||||
### To configure the OAuth2 server
|
||||
The OAuth2 server administrator must give you an OAuth2 client identifier and secret and enter this callback URL :
|
||||
`https://{CHAMILO_URL}/plugin/oauth2/src/callback.php`.
|
||||
|
||||
### To configure this plugin
|
||||
* Enable it
|
||||
* Fill in the setting parameters (read the help messages)
|
||||
* assign a region. Preferably `login_bottom`.
|
||||
|
||||
Also, you can configure the external login to work with the classic Chamilo form login.
|
||||
Adding this line in `configuration.php` file.
|
||||
```php
|
||||
$extAuthSource["oauth2"]["login"] = $_configuration['root_sys']."main/auth/external_login/login.oauth2.php";
|
||||
```
|
||||
14
plugin/oauth2/config.dist.php
Normal file
14
plugin/oauth2/config.dist.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Adjust user account values.
|
||||
*
|
||||
* @param array $response the Resource Owner Details
|
||||
* @param Chamilo\UserBundle\Entity\User $user the user
|
||||
* with firstname, lastname, status, username and email already updated
|
||||
* but unsaved
|
||||
*/
|
||||
function oauth2UpdateUserFromResourceOwnerDetails(array $response, Chamilo\UserBundle\Entity\User $user)
|
||||
{
|
||||
$user->setStatus(STUDENT);
|
||||
$user->setPhone($response['data'][0]['telephone']);
|
||||
}
|
||||
51
plugin/oauth2/index.php
Normal file
51
plugin/oauth2/index.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* @author Sébastien Ducoulombier <seb@ldd.fr>
|
||||
* inspired by AzureActiveDirectory plugin from Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.oauth2
|
||||
*/
|
||||
|
||||
/** @var OAuth2 $oAuth2Plugin */
|
||||
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||
use League\OAuth2\Client\Token\AccessToken;
|
||||
|
||||
$oAuth2Plugin = OAuth2::create();
|
||||
|
||||
if ($oAuth2Plugin->get(OAuth2::SETTING_ENABLE) === 'true') {
|
||||
$_template['block_title'] = $oAuth2Plugin->get(OAuth2::SETTING_BLOCK_NAME);
|
||||
|
||||
$_template['signin_url'] = $oAuth2Plugin->getSignInURL();
|
||||
|
||||
$managementLoginEnabled = 'true' === $oAuth2Plugin->get(OAuth2::SETTING_MANAGEMENT_LOGIN_ENABLE);
|
||||
|
||||
$_template['management_login_enabled'] = $managementLoginEnabled;
|
||||
|
||||
if ($managementLoginEnabled) {
|
||||
$managementLoginName = $oAuth2Plugin->get(OAuth2::SETTING_MANAGEMENT_LOGIN_NAME);
|
||||
|
||||
if (empty($managementLoginName)) {
|
||||
$managementLoginName = $oAuth2Plugin->get_lang('ManagementLogin');
|
||||
}
|
||||
|
||||
$_template['management_login_name'] = $managementLoginName;
|
||||
}
|
||||
|
||||
if (ChamiloSession::has('oauth2AccessToken')) {
|
||||
$accessToken = new AccessToken(ChamiloSession::read('oauth2AccessToken'));
|
||||
if ($accessToken->hasExpired()) {
|
||||
$provider = $oAuth2Plugin->getProvider();
|
||||
try {
|
||||
$newAccessToken = $provider->getAccessToken(
|
||||
'refresh_token',
|
||||
['refresh_token' => $accessToken->getRefreshToken()]
|
||||
);
|
||||
ChamiloSession::write('oauth2AccessToken', $newAccessToken->jsonSerialize());
|
||||
} catch (IdentityProviderException|\BadMethodCallException $e) {
|
||||
online_logout(null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
plugin/oauth2/install.php
Normal file
8
plugin/oauth2/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');
|
||||
}
|
||||
|
||||
OAuth2::create()->install();
|
||||
144
plugin/oauth2/lang/english.php
Normal file
144
plugin/oauth2/lang/english.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Strings to english L10n.
|
||||
*
|
||||
* @author Sébastien Ducoulombier <seb@ldd.fr>
|
||||
* inspired by Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.oauth2
|
||||
*/
|
||||
$strings['plugin_title'] = 'OAuth2';
|
||||
$strings['plugin_comment'] = 'Allow authentication with an <em>OAuth2</em> server';
|
||||
|
||||
$strings['enable'] = 'Enable';
|
||||
|
||||
$strings['force_redirect'] = 'Force redirect';
|
||||
$strings['force_redirect_help'] = 'If set to yes, then if the user is not yet logged in it will be redirected automatically to the SSO server';
|
||||
$strings['skip_force_redirect_in'] = 'Skip folders for force redirect';
|
||||
$strings['skip_force_redirect_in_help'] = "If force redirect is set to yes, then all pages will redirect unlogged user to the SSO server except from the one defined here in a list separated by commas in the form /main/webservices,/plugin/oauth2";
|
||||
|
||||
$strings['client_id'] = 'Client ID';
|
||||
$strings['client_id_help'] = '<strong>The <em>OAuth2</em> client identifier</strong>
|
||||
the <em>OAuth2</em> server administrator assigned to this Chamilo instance.
|
||||
<br/>Required.';
|
||||
|
||||
$strings['client_secret'] = 'Client Secret';
|
||||
$strings['client_secret_help'] = '<strong>The secret code</strong> associated to the <em>OAuth2</em> client identifier.
|
||||
<br/>Required.';
|
||||
|
||||
$strings['authorize_url'] = 'Authorize URL';
|
||||
$strings['authorize_url_help'] = 'The <em>OAuth2</em> server URL to request authorization.
|
||||
<br/>Required.';
|
||||
|
||||
$strings['scopes'] = 'Scopes';
|
||||
$strings['scopes_help'] = 'Scope is a mechanism in <em>OAuth2</em> to limit an application\'s access to a user\'s account.
|
||||
An application can request one or more scopes, this information is then presented to the user in the consent screen,
|
||||
and the access token issued to the application will be limited to the scopes granted.
|
||||
Multiple scopes should be set separeted with <code>,</code> or spaces.';
|
||||
|
||||
$strings['scope_separator'] = 'Scope separator';
|
||||
$strings['scope_separator'] = 'The separator used in the scope param. Defaul is a space. For instance: <code>email profile</code>.';
|
||||
|
||||
$strings['access_token_url'] = 'Access Token URL';
|
||||
$strings['access_token_url_help'] = 'The <em>OAuth2</em> server URL to request an access token.
|
||||
<br/>Required.';
|
||||
|
||||
$strings['access_token_method'] = 'Access Token HTTP Method';
|
||||
$strings['access_token_method_help'] = 'Default value: POST';
|
||||
|
||||
$strings['resource_owner_details_url'] = 'Resource Owner Details URL';
|
||||
$strings['resource_owner_details_url_help'] = 'The <em>OAuth2</em> server URL
|
||||
returning the identified user information as a <em>JSON</em> array.
|
||||
Required.';
|
||||
|
||||
$strings['response_error'] = 'Response error key';
|
||||
$strings['response_error_help'] = 'Default is <code>error</code>';
|
||||
|
||||
$strings['response_code'] = 'Response code key';
|
||||
$strings['response_code_help'] = 'By default, an error code retrieval is not attempted';
|
||||
|
||||
$strings['response_resource_owner_id'] = 'Response Resource Owner Id key';
|
||||
$strings['response_resource_owner_id_help'] = 'The array key to the user\'s <em>OAuth2</em> identifier value.
|
||||
<br/>Default value: <code>id</code>.
|
||||
<br/>If the identifier is in a subentry of the returned <em>JSON</em> array,
|
||||
<br/>then please enter successive path keys separated by dots. For example,
|
||||
<br/><code>data.0.id</code>
|
||||
<br/>means the identifier is to be found at
|
||||
<code>$jsonArray["data"][0]["id"]</code>';
|
||||
|
||||
$strings['update_user_info'] = 'Update user information';
|
||||
$strings['create_new_users'] = 'Create new users';
|
||||
$strings['response_resource_owner_firstname'] = 'Response Resource Owner firstname key';
|
||||
$strings['response_resource_owner_firstname_help'] = 'Same syntax as for the <em>Response Resource Owner Id key</em>';
|
||||
$strings['response_resource_owner_lastname'] = 'Response Resource Owner lastname key';
|
||||
$strings['response_resource_owner_status'] = 'Response Resource Owner status key';
|
||||
$strings['response_resource_owner_status_help'] = 'The value at this array key should be one of these integers:<dl>
|
||||
<dt>1</dt><dd>Course Manager / Teacher</dd>
|
||||
<dt>3</dt><dd>Session Administrator</dd>
|
||||
<dt>4</dt><dd>DRH</dd>
|
||||
<dt>5</dt><dd>Student</dd>
|
||||
<dt>6</dt><dd>Anonymous</dd>
|
||||
</dl>';
|
||||
$strings['response_resource_owner_teacher_status'] = 'Response Resource Owner status value for Course Manager / Teacher';
|
||||
$strings['response_resource_owner_teacher_status_help'] = 'If this value matches the value obtained from the <i>Response Resource Owner status key</i>, the user will have the role of Course Manager / Teacher';
|
||||
$strings['response_resource_owner_sessadmin_status'] = 'Response Resource Owner status value for Session Administrator';
|
||||
$strings['response_resource_owner_sessadmin_status_help'] = 'If this value matches the value obtained from the <i>Response Resource Owner status key</i>, the user will have the role of Session Administrator';
|
||||
$strings['response_resource_owner_drh_status'] = 'Response Resource Owner status value for HRM';
|
||||
$strings['response_resource_owner_drh_status_help'] = 'If this value matches the value obtained from the <i>Response Resource Owner status key</i>, the user will have the role of HRM';
|
||||
$strings['response_resource_owner_student_status'] = 'Response Resource Owner status value for Student';
|
||||
$strings['response_resource_owner_student_status_help'] = 'If this value matches the value obtained from the <i>Response Resource Owner status key</i>, the user will have the role of Student';
|
||||
$strings['response_resource_owner_anon_status'] = 'Response Resource Owner status value for Anonymous';
|
||||
$strings['response_resource_owner_anon_status_help'] = 'If this value matches the value obtained from the <i>Response Resource Owner status key</i>, the user will have the role of Anonymous';
|
||||
$strings['response_resource_owner_email'] = 'Response Resource Owner email key';
|
||||
$strings['response_resource_owner_username'] = 'Response Resource Owner username key';
|
||||
|
||||
$strings['response_resource_owner_urls'] = 'Response Resource Owner Access URL key';
|
||||
$strings['response_resource_owner_urls_help'] = 'Similar syntax as for the <em>Response Resource Owner Id key</em>,
|
||||
except there can be more than one value returned: <code>*</code> can be used as a placeholder for an integer.
|
||||
<code>*</code> will be replaced by <code>0</code>, then <code>1</code>, then <code>2</code> and so on while it matches.
|
||||
There can be more than one <code>*</code> in this key expression.
|
||||
<br/>This option is used when $_configuration[\'multiple_access_urls\'] is set in app/config/configuration.php.
|
||||
<br/>The fetched values should be found in table <code>access_url</code> columns <code>id</code> or <code>url</code>.
|
||||
<br/>Example:
|
||||
<br/><code>data.0.domaines.*.url</code>
|
||||
<br/>means the URLs would be found at
|
||||
<ul>
|
||||
<li><code>$jsonArray["data"]["domaines"][0]["url"]</code></li>
|
||||
<li><code>$jsonArray["data"]["domaines"][1]["url"]</code></li>
|
||||
<li><code>$jsonArray["data"]["domaines"][2]["url"]</code></li>
|
||||
<li>...</li>
|
||||
</ul>';
|
||||
|
||||
$strings['logout_url'] = 'Logout URL';
|
||||
$strings['logout_url_help'] = 'If set, the user agent will be redirected to this URL at logout.';
|
||||
|
||||
$strings['block_name'] = 'Block name';
|
||||
$strings['block_name_help'] = 'The title shown above the <em>OAuth2</em> Login button';
|
||||
|
||||
$strings['management_login_enable'] = 'Management login';
|
||||
$strings['management_login_enable_help'] = 'Disable the Chamilo login and enable an alternative login page for users.
|
||||
<br>
|
||||
You will need copy file <code>/plugin/oauth2/layout/login_form.tpl</code>
|
||||
to directory <code>/main/template/overrides/layout/</code>.';
|
||||
$strings['management_login_name'] = 'Name for the management login';
|
||||
$strings['management_login_name_help'] = 'Default value is "Management Login".';
|
||||
|
||||
$strings['allow_third_party_login'] = 'Allow third party login';
|
||||
|
||||
// please keep these below alphabetically sorted
|
||||
$strings['AccountInactive'] = "Account inactive";
|
||||
$strings['DefaultFirstname'] = 'OAuth2 User default firstname';
|
||||
$strings['DefaultLastname'] = 'OAuth2 User default lastname';
|
||||
$strings['FailedUserCreation'] = 'User account creation failed';
|
||||
$strings['InternalErrorCannotGetUserInfo'] = 'Internal error: could not get user information';
|
||||
$strings['InvalidJsonReceivedFromProvider'] = 'The OAuth2 provider did not provide a valid JSON document';
|
||||
$strings['ManagementLogin'] = 'Management Login';
|
||||
$strings['NoUserAccountAndUserCreationNotAllowed'] = 'This user doesn\'t have an account yet and auto-provisioning is not enabled. Please contact this portal administration team at %s to request access.';
|
||||
$strings['OAuth2Id'] = 'OAuth2 identifier';
|
||||
$strings['UserNotAllowedOnThisPortal'] = 'This user account is not enabled on this portal';
|
||||
$strings['WrongResponseResourceOwnerId'] = 'OAuth2 resource owner identifier value not found at the configured key';
|
||||
$strings['IssuerNotFound'] = 'Issuer not found';
|
||||
$strings['AuthorizeUrlNotAllowed'] = 'Authorize URL not allowed';
|
||||
|
||||
$strings['MessageInfoAboutRedirectToProvider'] = 'You are getting redirected to the common authentication system. Your credentials there are the ones that you typically use for other applications of your organisation. These might be different from the ones you used here previously.';
|
||||
44
plugin/oauth2/layout/login_form.tpl
Normal file
44
plugin/oauth2/layout/login_form.tpl
Normal file
@@ -0,0 +1,44 @@
|
||||
{% 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 oauth2_plugin_enabled = 'oauth2'|api_get_plugin_setting('enable') %}
|
||||
{% set oauth2_plugin_manage_login = 'oauth2'|api_get_plugin_setting('manage_login_enable') %}
|
||||
|
||||
{% if 'false' == oauth2_plugin_enabled or 'false' == oauth2_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' %}
|
||||
<li>
|
||||
<a href="{{ _p.web_main }}auth/lostPassword.php">{{ '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 %}
|
||||
BIN
plugin/oauth2/localLogin/images/footer.png
Normal file
BIN
plugin/oauth2/localLogin/images/footer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
plugin/oauth2/localLogin/images/header.png
Normal file
BIN
plugin/oauth2/localLogin/images/header.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
104
plugin/oauth2/localLogin/login.php
Normal file
104
plugin/oauth2/localLogin/login.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Local login form.
|
||||
*/
|
||||
require_once '../../../main/inc/global.inc.php';
|
||||
|
||||
/**
|
||||
* Homemade micro-controller.
|
||||
*/
|
||||
if (isset($_GET['loginFailed'])) {
|
||||
if (isset($_GET['error'])) {
|
||||
switch ($_GET['error']) {
|
||||
case 'account_expired':
|
||||
$error_message = get_lang('AccountExpired');
|
||||
break;
|
||||
case 'account_inactive':
|
||||
$error_message = get_lang('AccountInactive');
|
||||
break;
|
||||
case 'user_password_incorrect':
|
||||
$error_message = get_lang('InvalidId');
|
||||
break;
|
||||
case 'access_url_inactive':
|
||||
$error_message = get_lang('AccountURLInactive');
|
||||
break;
|
||||
default:
|
||||
$error_message = get_lang('InvalidId');
|
||||
}
|
||||
} else {
|
||||
$error_message = get_lang('InvalidId');
|
||||
}
|
||||
}
|
||||
|
||||
$rootWeb = api_get_path('WEB_PATH');
|
||||
|
||||
/**
|
||||
* HTML output.
|
||||
*/
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title>Local login</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="<?php echo $rootWeb; ?>web/assets/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="<?php echo $rootWeb; ?>web/assets/flag-icon-css/css/flag-icon.min.css" />
|
||||
|
||||
<script type="text/javascript" src="<?php echo $rootWeb; ?>web/assets/jquery/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="<?php echo $rootWeb; ?>web/assets/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
if (top.location != location) {
|
||||
top.location.href = document.location.href;
|
||||
}
|
||||
|
||||
// Handler pour la touche retour
|
||||
$('input').keyup(function(e) {
|
||||
if (e.keyCode == 13) {
|
||||
$('#login-form').submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div id="backgroundimage">
|
||||
<img src="<?php echo api_get_path(WEB_PATH); ?>/plugin/oauth2/localLogin/images/page-background.png" class="backgroundimage" />
|
||||
</div>-->
|
||||
<div id="wrapper">
|
||||
<div id="header">
|
||||
<img src="<?php echo api_get_path(WEB_PATH); ?>/plugin/oauth2/localLogin/images/header.png" alt="Logo" />
|
||||
</div> <!-- #header -->
|
||||
<div id="login-form-box" class="form-box">
|
||||
<div id="login-form-info" class="form-info">
|
||||
<?php
|
||||
echo Display::getFlashToString();
|
||||
Display::cleanFlashMessages();
|
||||
if (isset($content['info']) && !empty($content['info'])) {
|
||||
echo $content['info'];
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php if (isset($error_message)) {
|
||||
echo '<div id="login-form-info" class="form-error">'.$error_message.'</div>';
|
||||
}
|
||||
?>
|
||||
<form id="login-form" class="form" action="<?php echo api_get_path(WEB_PATH); ?>index.php" method="post">
|
||||
<div>
|
||||
<label for="login">*<?php echo get_lang('User'); ?></label>
|
||||
<input name="login" type="text" /><br />
|
||||
<label for="password">*<?php echo get_lang('Password'); ?></label>
|
||||
<input name="password" type="password" /><br />
|
||||
</div>
|
||||
</form>
|
||||
<div id="login-form-submit" class="form-submit" onclick="document.forms['login-form'].submit();">
|
||||
<span><?php echo get_lang('LoginEnter'); ?></span>
|
||||
</div> <!-- #form-submit -->
|
||||
</div> <!-- #form -->
|
||||
<div id="footer">
|
||||
<img src="<?php echo api_get_path(WEB_PATH); ?>/plugin/oauth2/localLogin/images/footer.png" />
|
||||
</div> <!-- #footer -->
|
||||
</div> <!-- #wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
35
plugin/oauth2/login.php
Normal file
35
plugin/oauth2/login.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
require __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$plugin = OAuth2::create();
|
||||
|
||||
$pluginEnabled = $plugin->get(OAuth2::SETTING_ENABLE);
|
||||
$managementLoginEnabled = $plugin->get(OAuth2::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(OAuth2::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('oauth2/view/login.tpl');
|
||||
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
}
|
||||
11
plugin/oauth2/plugin.php
Normal file
11
plugin/oauth2/plugin.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* @author Sébastien Ducoulombier <seb@ldd.fr>, inspired by Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.oauth2
|
||||
*/
|
||||
/** @var OAuth2 $plugin_info */
|
||||
$plugin_info = OAuth2::create()->get_info();
|
||||
|
||||
$plugin_info['templates'] = ['view/block.tpl'];
|
||||
37
plugin/oauth2/redirect_info.php
Normal file
37
plugin/oauth2/redirect_info.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
require __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$plugin = OAuth2::create();
|
||||
|
||||
if ('true' !== $plugin->get(OAuth2::SETTING_ENABLE)
|
||||
|| !ChamiloSession::has('oauth2state')
|
||||
|| !ChamiloSession::has('aouth2_authorization_url')
|
||||
) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$oauth2authorizationUrl = ChamiloSession::read('aouth2_authorization_url');
|
||||
|
||||
$htmlHeadXtra[] = '<meta http-equiv="refresh" content="15; url='.$oauth2authorizationUrl.'">';
|
||||
|
||||
ChamiloSession::erase('aouth2_authorization_url');
|
||||
|
||||
$content = '<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="alert alert-info text-center lead">
|
||||
<span class="fa fa-info-circle fa-2x fa-fw" aria-hidden="true"></span>
|
||||
'.$plugin->get_lang('MessageInfoAboutRedirectToProvider').'
|
||||
<hr>
|
||||
'.$plugin->get_lang('PleaseWaitThisCouldTakeAWhile').'
|
||||
<span class="fa fa-spinner fa-pulse fa-fw" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$template = new Template();
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
563
plugin/oauth2/src/OAuth2.php
Normal file
563
plugin/oauth2/src/OAuth2.php
Normal file
@@ -0,0 +1,563 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\ExtraFieldValues;
|
||||
use Chamilo\CoreBundle\Entity\TrackELogin;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use League\OAuth2\Client\Provider\AbstractProvider;
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||
use League\OAuth2\Client\Provider\GenericProvider;
|
||||
use League\OAuth2\Client\Token\AccessToken;
|
||||
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
|
||||
|
||||
/**
|
||||
* OAuth2 plugin class.
|
||||
*
|
||||
* @author Sébastien Ducoulombier <seb@ldd.fr>
|
||||
* inspired by AzureActiveDirectory plugin class from Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*
|
||||
* @package chamilo.plugin.oauth2
|
||||
*/
|
||||
class OAuth2 extends Plugin
|
||||
{
|
||||
use ArrayAccessorTrait;
|
||||
|
||||
public const SETTING_ENABLE = 'enable';
|
||||
|
||||
public const SETTING_FORCE_REDIRECT = 'force_redirect';
|
||||
public const SETTING_SKIP_FORCE_REDIRECT_IN = 'skip_force_redirect_in';
|
||||
|
||||
public const SETTING_CLIENT_ID = 'client_id';
|
||||
public const SETTING_CLIENT_SECRET = 'client_secret';
|
||||
|
||||
public const SETTING_AUTHORIZE_URL = 'authorize_url';
|
||||
public const SETTING_SCOPES = 'scopes';
|
||||
public const SETTING_SCOPE_SEPARATOR = 'scope_separator';
|
||||
|
||||
public const SETTING_ACCESS_TOKEN_URL = 'access_token_url';
|
||||
public const SETTING_ACCESS_TOKEN_METHOD = 'access_token_method';
|
||||
// const SETTING_ACCESS_TOKEN_RESOURCE_OWNER_ID = 'access_token_resource_owner_id';
|
||||
|
||||
public const SETTING_RESOURCE_OWNER_DETAILS_URL = 'resource_owner_details_url';
|
||||
|
||||
public const SETTING_RESPONSE_ERROR = 'response_error';
|
||||
public const SETTING_RESPONSE_CODE = 'response_code';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_ID = 'response_resource_owner_id';
|
||||
|
||||
public const SETTING_UPDATE_USER_INFO = 'update_user_info';
|
||||
public const SETTING_CREATE_NEW_USERS = 'create_new_users';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_FIRSTNAME = 'response_resource_owner_firstname';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_LASTNAME = 'response_resource_owner_lastname';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_STATUS = 'response_resource_owner_status';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_TEACHER_STATUS = 'response_resource_owner_teacher_status';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_SESSADMIN_STATUS = 'response_resource_owner_sessadmin_status';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_DRH_STATUS = 'response_resource_owner_drh_status';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_STUDENT_STATUS = 'response_resource_owner_student_status';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_ANON_STATUS = 'response_resource_owner_anon_status';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_EMAIL = 'response_resource_owner_email';
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_USERNAME = 'response_resource_owner_username';
|
||||
|
||||
public const SETTING_RESPONSE_RESOURCE_OWNER_URLS = 'response_resource_owner_urls';
|
||||
|
||||
public const SETTING_LOGOUT_URL = 'logout_url';
|
||||
|
||||
public const SETTING_BLOCK_NAME = 'block_name';
|
||||
|
||||
public const SETTING_MANAGEMENT_LOGIN_ENABLE = 'management_login_enable';
|
||||
public const SETTING_MANAGEMENT_LOGIN_NAME = 'management_login_name';
|
||||
|
||||
public const SETTING_ALLOW_THIRD_PARTY_LOGIN = 'allow_third_party_login';
|
||||
|
||||
public const EXTRA_FIELD_OAUTH2_ID = 'oauth2_id';
|
||||
|
||||
private const DEBUG = false;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'0.1',
|
||||
'Sébastien Ducoulombier',
|
||||
[
|
||||
self::SETTING_ENABLE => 'boolean',
|
||||
|
||||
self::SETTING_FORCE_REDIRECT => 'boolean',
|
||||
self::SETTING_SKIP_FORCE_REDIRECT_IN => 'text',
|
||||
|
||||
self::SETTING_CLIENT_ID => 'text',
|
||||
self::SETTING_CLIENT_SECRET => 'text',
|
||||
|
||||
self::SETTING_AUTHORIZE_URL => 'text',
|
||||
self::SETTING_SCOPES => 'text',
|
||||
self::SETTING_SCOPE_SEPARATOR => 'text',
|
||||
|
||||
self::SETTING_ACCESS_TOKEN_URL => 'text',
|
||||
self::SETTING_ACCESS_TOKEN_METHOD => [
|
||||
'type' => 'select',
|
||||
'options' => [
|
||||
AbstractProvider::METHOD_POST => 'POST',
|
||||
AbstractProvider::METHOD_GET => 'GET',
|
||||
],
|
||||
],
|
||||
// self::SETTING_ACCESS_TOKEN_RESOURCE_OWNER_ID => 'text',
|
||||
|
||||
self::SETTING_RESOURCE_OWNER_DETAILS_URL => 'text',
|
||||
|
||||
self::SETTING_RESPONSE_ERROR => 'text',
|
||||
self::SETTING_RESPONSE_CODE => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_ID => 'text',
|
||||
|
||||
self::SETTING_UPDATE_USER_INFO => 'boolean',
|
||||
self::SETTING_CREATE_NEW_USERS => 'boolean',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_FIRSTNAME => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_LASTNAME => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_STATUS => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_TEACHER_STATUS => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_SESSADMIN_STATUS => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_DRH_STATUS => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_STUDENT_STATUS => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_ANON_STATUS => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_EMAIL => 'text',
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_USERNAME => 'text',
|
||||
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_URLS => 'text',
|
||||
|
||||
self::SETTING_LOGOUT_URL => 'text',
|
||||
|
||||
self::SETTING_BLOCK_NAME => 'text',
|
||||
|
||||
self::SETTING_MANAGEMENT_LOGIN_ENABLE => 'boolean',
|
||||
self::SETTING_MANAGEMENT_LOGIN_NAME => 'text',
|
||||
|
||||
self::SETTING_ALLOW_THIRD_PARTY_LOGIN => 'boolean',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance the plugin.
|
||||
*
|
||||
* @staticvar null $result
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public static function create(): OAuth2
|
||||
{
|
||||
static $result = null;
|
||||
|
||||
return $result ?: $result = new self();
|
||||
}
|
||||
|
||||
public function getProvider(): GenericProvider
|
||||
{
|
||||
$redirectUri = api_get_path(WEB_PLUGIN_PATH).'oauth2/src/callback.php';
|
||||
// In cases not precisely defined yet, this alternative version might be necessary - see BT#20611
|
||||
//$redirectUri = api_get_path(WEB_PATH).'authorization-code/callback';
|
||||
$options = [
|
||||
'clientId' => $this->get(self::SETTING_CLIENT_ID),
|
||||
'clientSecret' => $this->get(self::SETTING_CLIENT_SECRET),
|
||||
'redirectUri' => $redirectUri,
|
||||
'urlAuthorize' => $this->get(self::SETTING_AUTHORIZE_URL),
|
||||
'urlResourceOwnerDetails' => $this->get(self::SETTING_RESOURCE_OWNER_DETAILS_URL),
|
||||
];
|
||||
|
||||
if ('' === $scopeSeparator = (string) $this->get(self::SETTING_SCOPE_SEPARATOR)) {
|
||||
$scopeSeparator = ' ';
|
||||
}
|
||||
|
||||
$options['scopeSeparator'] = $scopeSeparator;
|
||||
|
||||
if ('' !== $scopes = (string) $this->get(self::SETTING_SCOPES)) {
|
||||
$options['scopes'] = explode($scopeSeparator, $scopes);
|
||||
}
|
||||
|
||||
if ('' !== $urlAccessToken = (string) $this->get(self::SETTING_ACCESS_TOKEN_URL)) {
|
||||
$options['urlAccessToken'] = $urlAccessToken;
|
||||
}
|
||||
|
||||
if ('' !== $accessTokenMethod = (string) $this->get(self::SETTING_ACCESS_TOKEN_METHOD)) {
|
||||
$options['accessTokenMethod'] = $accessTokenMethod;
|
||||
}
|
||||
|
||||
// if ('' !== $accessTokenResourceOwnerId = (string) $this->get(self::SETTING_ACCESS_TOKEN_RESOURCE_OWNER_ID)) {
|
||||
// $options['accessTokenResourceOwnerId'] = $accessTokenResourceOwnerId;
|
||||
// }
|
||||
|
||||
if ('' !== $responseError = (string) $this->get(self::SETTING_RESPONSE_ERROR)) {
|
||||
$options['responseError'] = $responseError;
|
||||
}
|
||||
|
||||
if ('' !== $responseCode = (string) $this->get(self::SETTING_RESPONSE_CODE)) {
|
||||
$options['responseCode'] = $responseCode;
|
||||
}
|
||||
|
||||
if ('' !== $responseResourceOwnerId = (string) $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_ID)) {
|
||||
$options['responseResourceOwnerId'] = $responseResourceOwnerId;
|
||||
}
|
||||
|
||||
return new GenericProvider($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IdentityProviderException
|
||||
*
|
||||
* @return array user information, as returned by api_get_user_info(userId)
|
||||
*/
|
||||
public function getUserInfo(GenericProvider $provider, AccessToken $accessToken): array
|
||||
{
|
||||
$url = $provider->getResourceOwnerDetailsUrl($accessToken);
|
||||
$request = $provider->getAuthenticatedRequest($provider::METHOD_GET, $url, $accessToken);
|
||||
$response = $provider->getParsedResponse($request);
|
||||
$this->log('response', print_r($response, true));
|
||||
|
||||
if (false === is_array($response)) {
|
||||
$this->log('invalid response', print_r($response, true));
|
||||
throw new UnexpectedValueException($this->get_lang('InvalidJsonReceivedFromProvider'));
|
||||
}
|
||||
$resourceOwnerId = $this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_ID)
|
||||
);
|
||||
if (empty($resourceOwnerId)) {
|
||||
$this->log('missing setting', 'response_resource_owner_id');
|
||||
throw new RuntimeException($this->get_lang('WrongResponseResourceOwnerId'));
|
||||
}
|
||||
$this->log('response resource owner id', $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_ID));
|
||||
$extraFieldValue = new ExtraFieldValue('user');
|
||||
$result = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
|
||||
self::EXTRA_FIELD_OAUTH2_ID,
|
||||
$resourceOwnerId
|
||||
);
|
||||
if (false === $result) {
|
||||
$this->log('user not found', "extrafield 'oauth2_id' with value '$resourceOwnerId'");
|
||||
|
||||
$username = $this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_USERNAME),
|
||||
'oauth2user_'.$resourceOwnerId
|
||||
);
|
||||
|
||||
$userInfo = api_get_user_info_from_username($username);
|
||||
|
||||
if (false !== $userInfo && !empty($userInfo['id']) && 'platform' === $userInfo['auth_source']) {
|
||||
$this->log('platform user exists', print_r($userInfo, true));
|
||||
|
||||
$userId = $userInfo['id'];
|
||||
} else {
|
||||
// authenticated user not found in internal database
|
||||
if ('true' !== $this->get(self::SETTING_CREATE_NEW_USERS)) {
|
||||
$this->log('exception', 'create_new_users setting is disabled');
|
||||
$message = sprintf(
|
||||
$this->get_lang('NoUserAccountAndUserCreationNotAllowed'),
|
||||
Display::encrypted_mailto_link(api_get_setting('emailAdministrator'))
|
||||
);
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
require_once __DIR__.'/../../../main/auth/external_login/functions.inc.php';
|
||||
|
||||
$firstName = $this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_FIRSTNAME),
|
||||
$this->get_lang('DefaultFirstname')
|
||||
);
|
||||
$lastName = $this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_LASTNAME),
|
||||
$this->get_lang('DefaultLastname')
|
||||
);
|
||||
$status = $this->mapUserStatusFromResponse($response);
|
||||
$email = $this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_EMAIL),
|
||||
'oauth2user_'.$resourceOwnerId.'@'.(gethostname() or 'localhost')
|
||||
);
|
||||
|
||||
$userInfo = [
|
||||
'firstname' => $firstName,
|
||||
'lastname' => $lastName,
|
||||
'status' => $status,
|
||||
'email' => $email,
|
||||
'username' => $username,
|
||||
'auth_source' => 'oauth2',
|
||||
];
|
||||
$userId = external_add_user($userInfo);
|
||||
if (false === $userId) {
|
||||
$this->log('user not created', print_r($userInfo, true));
|
||||
throw new RuntimeException($this->get_lang('FailedUserCreation'));
|
||||
}
|
||||
$this->log('user created', (string) $userId);
|
||||
}
|
||||
|
||||
$this->updateUser($userId, $response);
|
||||
// Not checking function update_extra_field_value return value because not reliable
|
||||
UserManager::update_extra_field_value($userId, self::EXTRA_FIELD_OAUTH2_ID, $resourceOwnerId);
|
||||
$this->updateUserUrls($userId, $response);
|
||||
} else {
|
||||
$this->log('user found', "extrafield 'oauth2_id' with value '$resourceOwnerId'");
|
||||
// authenticated user found in internal database
|
||||
if (is_array($result) and array_key_exists('item_id', $result)) {
|
||||
$userId = $result['item_id'];
|
||||
} else {
|
||||
$userId = $result;
|
||||
}
|
||||
if ('true' === $this->get(self::SETTING_UPDATE_USER_INFO)) {
|
||||
$this->updateUser($userId, $response);
|
||||
$this->updateUserUrls($userId, $response);
|
||||
|
||||
Event::addEvent(LOG_USER_UPDATE, LOG_USER_ID, $userId);
|
||||
}
|
||||
}
|
||||
$userInfo = api_get_user_info($userId);
|
||||
if (empty($userInfo)) {
|
||||
$this->log('user info not found', (string) $userId);
|
||||
throw new LogicException($this->get_lang('InternalErrorCannotGetUserInfo'));
|
||||
}
|
||||
|
||||
$this->log('user info', print_r($userInfo, true));
|
||||
|
||||
return $userInfo;
|
||||
}
|
||||
|
||||
public function getSignInURL(): string
|
||||
{
|
||||
return api_get_path(WEB_PLUGIN_PATH).$this->get_name().'/src/callback.php';
|
||||
// In cases not precisely defined yet, this alternative version might be necessary - see BT#20611
|
||||
//return api_get_path(WEB_PATH).'authorization-code/callback';
|
||||
}
|
||||
|
||||
public function getLogoutUrl(): string
|
||||
{
|
||||
$token = ChamiloSession::read('oauth2AccessToken');
|
||||
$idToken = !empty($token['id_token']) ? $token['id_token'] : null;
|
||||
|
||||
return $this->get(self::SETTING_LOGOUT_URL).'?'.http_build_query(
|
||||
[
|
||||
'id_token_hint' => $idToken,
|
||||
'post_logout_redirect_uri' => api_get_path(WEB_PATH),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create extra fields for user when installing.
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
UserManager::create_extra_field(
|
||||
self::EXTRA_FIELD_OAUTH2_ID,
|
||||
ExtraField::FIELD_TYPE_TEXT,
|
||||
$this->get_lang('OAuth2Id'),
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
public static function isFirstLoginAfterAuthSource(int $userId): bool
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
$lastLogin = $em
|
||||
->getRepository(TrackELogin::class)
|
||||
->findOneBy(
|
||||
['loginUserId' => $userId],
|
||||
['loginDate' => 'DESC']
|
||||
)
|
||||
;
|
||||
|
||||
if (!$lastLogin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objExtraField = new ExtraField('user');
|
||||
$field = $objExtraField->getHandlerEntityByFieldVariable(self::EXTRA_FIELD_OAUTH2_ID);
|
||||
|
||||
$fieldValue = $em
|
||||
->getRepository(ExtraFieldValues::class)
|
||||
->findOneBy(
|
||||
['itemId' => $userId, 'field' => $field]
|
||||
)
|
||||
;
|
||||
|
||||
if (!$fieldValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $fieldValue->getCreatedAt() >= $lastLogin->getLoginDate();
|
||||
}
|
||||
|
||||
private function mapUserStatusFromResponse(array $response, int $defaultStatus = STUDENT): int
|
||||
{
|
||||
$status = $this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_STATUS),
|
||||
$defaultStatus
|
||||
);
|
||||
|
||||
$responseStatus = [];
|
||||
|
||||
if ($teacherStatus = $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_TEACHER_STATUS)) {
|
||||
$responseStatus[COURSEMANAGER] = $teacherStatus;
|
||||
}
|
||||
|
||||
if ($sessAdminStatus = $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_SESSADMIN_STATUS)) {
|
||||
$responseStatus[SESSIONADMIN] = $sessAdminStatus;
|
||||
}
|
||||
|
||||
if ($drhStatus = $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_DRH_STATUS)) {
|
||||
$responseStatus[DRH] = $drhStatus;
|
||||
}
|
||||
|
||||
if ($studentStatus = $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_STUDENT_STATUS)) {
|
||||
$responseStatus[STUDENT] = $studentStatus;
|
||||
}
|
||||
|
||||
if ($anonStatus = $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_ANON_STATUS)) {
|
||||
$responseStatus[ANONYMOUS] = $anonStatus;
|
||||
}
|
||||
|
||||
$map = array_flip($responseStatus);
|
||||
|
||||
return $map[$status] ?? $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends ArrayAccessorTrait::getValueByKey to return a list of values
|
||||
* $key can contain wild card character *
|
||||
* It will be replaced by 0, 1, 2 and so on as long as the resulting key exists in $data
|
||||
* This is a recursive function, allowing for more than one occurrence of the wild card character.
|
||||
*/
|
||||
private function getValuesByKey(array $data, string $key, array $default = []): array
|
||||
{
|
||||
if (!is_string($key) || empty($key) || !count($data)) {
|
||||
return $default;
|
||||
}
|
||||
$pos = strpos($key, '*');
|
||||
if ($pos === false) {
|
||||
$value = $this->getValueByKey($data, $key);
|
||||
|
||||
return is_null($value) ? [] : [$value];
|
||||
}
|
||||
$values = [];
|
||||
$beginning = substr($key, 0, $pos);
|
||||
$remaining = substr($key, $pos + 1);
|
||||
$index = 0;
|
||||
do {
|
||||
$newValues = $this->getValuesByKey(
|
||||
$data,
|
||||
$beginning.$index.$remaining
|
||||
);
|
||||
$values = array_merge($values, $newValues);
|
||||
$index++;
|
||||
} while ($newValues);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function updateUser($userId, $response)
|
||||
{
|
||||
$user = UserManager::getRepository()->find($userId);
|
||||
$user->setFirstname(
|
||||
$this->getValueByKey(
|
||||
$response,
|
||||
$this->get(
|
||||
self::SETTING_RESPONSE_RESOURCE_OWNER_FIRSTNAME
|
||||
),
|
||||
$user->getFirstname()
|
||||
)
|
||||
);
|
||||
$user->setLastname(
|
||||
$this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_LASTNAME),
|
||||
$user->getLastname()
|
||||
)
|
||||
);
|
||||
$user->setUserName(
|
||||
$this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_USERNAME),
|
||||
$user->getUsername()
|
||||
)
|
||||
);
|
||||
$user->setEmail(
|
||||
$this->getValueByKey(
|
||||
$response,
|
||||
$this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_EMAIL),
|
||||
$user->getEmail()
|
||||
)
|
||||
);
|
||||
$status = $this->mapUserStatusFromResponse(
|
||||
$response,
|
||||
$user->getStatus()
|
||||
);
|
||||
$user->setStatus($status);
|
||||
$user->setAuthSource('oauth2');
|
||||
$configFilePath = __DIR__.'/../config.php';
|
||||
if (file_exists($configFilePath)) {
|
||||
require_once $configFilePath;
|
||||
$functionName = 'oauth2UpdateUserFromResourceOwnerDetails';
|
||||
if (function_exists($functionName)) {
|
||||
$functionName($response, $user);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
UserManager::getManager()->updateUser($user);
|
||||
} catch (UniqueConstraintViolationException $exception) {
|
||||
throw new Exception(get_lang('UserNameUsedTwice'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Access URLs associated to a user
|
||||
* according to the OAuth2 server response resource owner
|
||||
* if multi-URL is enabled and SETTING_RESPONSE_RESOURCE_OWNER_URLS defined.
|
||||
*
|
||||
* @param $userId integer
|
||||
* @param $response array
|
||||
*/
|
||||
private function updateUserUrls($userId, $response)
|
||||
{
|
||||
if (api_is_multiple_url_enabled()) {
|
||||
$key = (string) $this->get(self::SETTING_RESPONSE_RESOURCE_OWNER_URLS);
|
||||
if (!empty($key)) {
|
||||
$availableUrls = [];
|
||||
foreach (UrlManager::get_url_data() as $existingUrl) {
|
||||
$urlId = $existingUrl['id'];
|
||||
$availableUrls[strval($urlId)] = $urlId;
|
||||
$availableUrls[$existingUrl['url']] = $urlId;
|
||||
}
|
||||
$allowedUrlIds = [];
|
||||
foreach ($this->getValuesByKey($response, $key) as $value) {
|
||||
if (array_key_exists($value, $availableUrls)) {
|
||||
$allowedUrlIds[] = $availableUrls[$value];
|
||||
} else {
|
||||
$newValue = ($value[-1] === '/') ? substr($value, 0, -1) : $value.'/';
|
||||
if (array_key_exists($newValue, $availableUrls)) {
|
||||
$allowedUrlIds[] = $availableUrls[$newValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
$grantedUrlIds = [];
|
||||
foreach (UrlManager::get_access_url_from_user($userId) as $grantedUrl) {
|
||||
$grantedUrlIds[] = $grantedUrl['access_url_id'];
|
||||
}
|
||||
foreach (array_diff($grantedUrlIds, $allowedUrlIds) as $extraUrlId) {
|
||||
UrlManager::delete_url_rel_user($userId, $extraUrlId);
|
||||
}
|
||||
foreach (array_diff($allowedUrlIds, $grantedUrlIds) as $missingUrlId) {
|
||||
UrlManager::add_user_to_url($userId, $missingUrlId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function log(string $key, string $content)
|
||||
{
|
||||
if (self::DEBUG) {
|
||||
error_log("OAuth2 plugin: $key: $content");
|
||||
}
|
||||
}
|
||||
}
|
||||
100
plugin/oauth2/src/callback.php
Normal file
100
plugin/oauth2/src/callback.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use League\OAuth2\Client\Token\AccessToken;
|
||||
|
||||
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 = OAuth2::create();
|
||||
|
||||
if ('true' !== $plugin->get(OAuth2::SETTING_ENABLE)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$provider = $plugin->getProvider();
|
||||
|
||||
// If we don't have an authorization code then get one
|
||||
if (!array_key_exists('code', $_GET)) {
|
||||
// Fetch the authorization URL from the provider; this returns the
|
||||
// urlAuthorize option and generates and applies any necessary parameters
|
||||
// (e.g. state).
|
||||
$authorizationUrl = $provider->getAuthorizationUrl();
|
||||
|
||||
// Get the state generated for you and store it to the session.
|
||||
ChamiloSession::write('oauth2state', $provider->getState());
|
||||
|
||||
// Redirect the user to the authorization URL.
|
||||
header('Location: '.$authorizationUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check given state against previously stored one to mitigate CSRF attack
|
||||
if (!array_key_exists('state', $_GET) || ($_GET['state'] !== ChamiloSession::read('oauth2state'))) {
|
||||
ChamiloSession::erase('oauth2state');
|
||||
exit('Invalid state');
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to get an access token using the authorization code grant.
|
||||
/**
|
||||
* @var $accessToken AccessToken
|
||||
*/
|
||||
$accessToken = $provider->getAccessToken(
|
||||
'authorization_code',
|
||||
['code' => $_GET['code']]
|
||||
);
|
||||
ChamiloSession::write('oauth2AccessToken', $accessToken->jsonSerialize());
|
||||
$userInfo = $plugin->getUserInfo($provider, $accessToken);
|
||||
if ($userInfo['active'] != '1') {
|
||||
throw new Exception($plugin->get_lang('AccountInactive'));
|
||||
}
|
||||
if (api_is_multiple_url_enabled()) {
|
||||
$userId = $userInfo['user_id'];
|
||||
$urlIdsTheUserCanAccess = api_get_access_url_from_user($userId);
|
||||
$userCanAccessTheFirstURL = in_array(1, $urlIdsTheUserCanAccess);
|
||||
$userCanAccessTheCurrentURL = in_array(api_get_current_access_url_id(), $urlIdsTheUserCanAccess)
|
||||
|| UserManager::is_admin($userId)
|
||||
&& $userCanAccessTheFirstURL;
|
||||
|
||||
if (!$userCanAccessTheCurrentURL) {
|
||||
throw new Exception($plugin->get_lang('UserNotAllowedOnThisPortal'));
|
||||
}
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
$message = Display::return_message($exception->getMessage(), 'error', false);
|
||||
Display::addFlash($message);
|
||||
header('Location: '.api_get_path(WEB_PATH));
|
||||
exit;
|
||||
}
|
||||
|
||||
ConditionalLogin::check_conditions($userInfo);
|
||||
|
||||
$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', 'oauth2');
|
||||
|
||||
Redirect::session_request_uri(true, $userInfo['user_id']);
|
||||
54
plugin/oauth2/src/oidc_login.php
Normal file
54
plugin/oauth2/src/oidc_login.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
|
||||
require __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
$httpRequest = HttpRequest::createFromGlobals();
|
||||
|
||||
$plugin = OAuth2::create();
|
||||
|
||||
$iss = $httpRequest->get('iss');
|
||||
$loginHint = $httpRequest->get('login_hint');
|
||||
$targetLinkUri = $httpRequest->get('target_link_uri');
|
||||
|
||||
try {
|
||||
if ('true' !== $plugin->get(OAuth2::SETTING_ENABLE)
|
||||
|| 'true' !== $plugin->get(OAuth2::SETTING_ALLOW_THIRD_PARTY_LOGIN)
|
||||
) {
|
||||
throw new Exception(get_lang('NotAllowedHere'));
|
||||
}
|
||||
|
||||
if (empty($iss)) {
|
||||
throw new Exception($plugin->get_lang('IssuerNotFound'));
|
||||
}
|
||||
|
||||
$authorizeUrlSetting = $plugin->get(OAuth2::SETTING_AUTHORIZE_URL);
|
||||
|
||||
if (empty($authorizeUrlSetting) || 0 !== strpos($authorizeUrlSetting, $iss)) {
|
||||
throw new Exception($plugin->get_lang('AuthorizeUrlNotAllowed'));
|
||||
}
|
||||
|
||||
$provider = $plugin->getProvider();
|
||||
|
||||
$authorizationUrl = $provider->getAuthorizationUrl(
|
||||
[
|
||||
'login_hint' => $loginHint,
|
||||
'target_link_uri' => $targetLinkUri,
|
||||
]
|
||||
);
|
||||
|
||||
ChamiloSession::write('oauth2state', $provider->getState());
|
||||
|
||||
$httpResponse = new RedirectResponse($authorizationUrl);
|
||||
$httpResponse->send();
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage()
|
||||
? Display::return_message($e->getMessage(), 'error')
|
||||
: null;
|
||||
|
||||
api_not_allowed(true, $message);
|
||||
}
|
||||
18
plugin/oauth2/view/block.tpl
Normal file
18
plugin/oauth2/view/block.tpl
Normal file
@@ -0,0 +1,18 @@
|
||||
{% if not _u.logged %}
|
||||
<div id="oauth2-login">
|
||||
{% if not oauth2.block_title is empty %}
|
||||
<h4>{{ oauth2.block_title }}</h4>
|
||||
{% endif %}
|
||||
|
||||
{% if not oauth2.signin_url is empty %}
|
||||
<a href="{{ oauth2.signin_url }}" class="btn btn-default">{{ 'SignIn'|get_lang }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if oauth2.management_login_enabled %}
|
||||
<hr>
|
||||
<a href="{{ _p.web_plugin ~ 'oauth2/login.php' }}">
|
||||
{{ oauth2.management_login_name }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
19
plugin/oauth2/view/login.tpl
Normal file
19
plugin/oauth2/view/login.tpl
Normal file
@@ -0,0 +1,19 @@
|
||||
<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' %}
|
||||
<li><a href="{{ _p.web_main }}auth/lostPassword.php">{{ 'LostPassword'|get_lang }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user