Add keycloack plugin BT#15160

pull/2798/head
Julio Montoya 7 years ago
parent db5b1d84e8
commit 83031035d0
  1. 3
      composer.json
  2. 9
      main/inc/lib/online.inc.php
  3. 10
      main/inc/lib/template.lib.php
  4. 59
      plugin/keycloak/KeycloakPlugin.php
  5. 27
      plugin/keycloak/index.php
  6. 6
      plugin/keycloak/lang/english.php
  7. 33
      plugin/keycloak/metadata.php
  8. 4
      plugin/keycloak/plugin.php
  9. 148
      plugin/keycloak/settings.dist.php
  10. 192
      plugin/keycloak/start.php

@ -108,7 +108,8 @@
"ocramius/proxy-manager": "~1.0|2.0.*",
"culqi/culqi-php": "1.3.4",
"knplabs/knp-components": "~1.3",
"guzzlehttp/guzzle": "~6.0"
"guzzlehttp/guzzle": "~6.0",
"onelogin/php-saml": "^3.0"
},
"require-dev": {
"behat/behat": "@stable",

@ -176,8 +176,15 @@ function online_logout($user_id = null, $logout_redirect = false)
session_regenerate_id();
Session::destroy();
$pluginKeycloak = api_get_plugin_setting('keycloak', 'tool_enable') === 'true';
if ($pluginKeycloak) {
$pluginUrl = api_get_path(WEB_PLUGIN_PATH).'keycloak/start.php?slo';
header('Location: '.$pluginUrl);
exit;
}
if ($logout_redirect) {
header("Location: ".$url);
header("Location: $url");
exit;
}
}

@ -1192,6 +1192,16 @@ class Template
$html .= '<div>'.openid_form().'</div>';
}
$pluginKeycloak = api_get_plugin_setting('keycloak', 'tool_enable') === 'true';
$plugin = null;
if ($pluginKeycloak) {
$pluginUrl = api_get_path(WEB_PLUGIN_PATH).'keycloak/start.php?sso';
$pluginUrl = Display::url('Keycloak', $pluginUrl, ['class' => 'btn btn-primary']);
$html .= '<div>'.$pluginUrl.'</div>';
}
$html .= '<div></div>';
return $html;
}

@ -0,0 +1,59 @@
<?php
/* For license terms, see /license.txt */
use ChamiloSession as Session;
/**
* Class Keycloak.
*/
class KeycloakPlugin extends Plugin
{
/**
* Keycloak constructor.
*/
protected function __construct()
{
parent::__construct(
'1.1',
'Julio Montoya',
[
'tool_enable' => 'boolean',
]
);
}
/**
* @return $this
*/
public static function create()
{
static $result = null;
return $result ? $result : $result = new self();
}
/**
* @return string
*/
public function get_block_title()
{
return $this->get('block_title');
}
/**
* @return string
*/
public function get_content()
{
return $this->get('content');
}
public function logout()
{
Session::erase('samlUserdata');
Session::erase('samlNameId');
Session::erase('samlNameIdFormat');
Session::erase('samlSessionIndex');
Session::erase('AuthNRequestID');
}
}

@ -0,0 +1,27 @@
<?php
/* For license terms, see /license.txt */
$plugin = KeycloakPlugin::create();
$content = $plugin->get_content();
$title = $plugin->get_block_title();
$title = $title ? "<h4>$title</h4>" : '';
$css = $plugin->get_css();
$css = $css ? "<style type=\"text/css\" scoped=\"scoped\">$css</style>" : '';
if (empty($content)) {
echo '';
}
echo <<<EOT
<div class="well sidebar-nav static">
$css
<div class="menusection">
$title
<div class="content">
$content
</div>
</div>
</div>
EOT;

@ -0,0 +1,6 @@
<?php
$strings['plugin_title'] = 'Keycloak';
$strings['plugin_comment'] = 'Keycloak login integration with Chamilo';
$strings['block_title'] = 'Keycloak';
$strings['tool_enable'] = 'Enabled';

@ -0,0 +1,33 @@
<?php
/* For license terms, see /license.txt */
require_once '../../main/inc/global.inc.php';
$pluginKeycloak = api_get_plugin_setting('keycloak', 'tool_enable') === 'true';
if (!$pluginKeycloak) {
api_not_allowed(true);
}
/**
* SAML Metadata view
*/
require_once 'settings.php';
try {
// Now we only validate SP settings
$settings = new \OneLogin\Saml2\Settings($settingsInfo, true);
$metadata = $settings->getSPMetadata();
$errors = $settings->validateMetadata($metadata);
if (empty($errors)) {
header('Content-Type: text/xml');
echo $metadata;
} else {
throw new OneLogin\Saml2\Error(
'Invalid SP metadata: '.implode(', ', $errors),
OneLogin\Saml2\Error::METADATA_SP_INVALID
);
}
} catch (Exception $e) {
echo $e->getMessage();
}

@ -0,0 +1,4 @@
<?php
/* For license terms, see /license.txt */
$plugin_info = KeycloakPlugin::create()->get_info();

@ -0,0 +1,148 @@
<?php
/* For license terms, see /license.txt */
$spBaseUrl = api_get_path(WEB_PATH).'plugin/keycloak/';
$settingsInfo = array(
'strict' => false,
'debug' => true,
'sp' => array (
'entityId' => $spBaseUrl.'metadata.php',
'assertionConsumerService' => array(
'url' => $spBaseUrl.'start.php?acs',
),
'singleLogoutService' => array (
'url' => $spBaseUrl.'start.php?sls',
),
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
),
'idp' => array(
'entityId' => '', // Example http://localhost:8080/auth/realms/master
'singleSignOnService' => array (
'url' => '', // example http://localhost:8080/auth/realms/master/protocol/saml
),
'singleLogoutService' => array (
'url' => '', // example http://localhost:8080/auth/realms/master/protocol/saml
),
'x509cert' => '',
),
);
// advanced settings
//
//
//// Compression settings
//'compress' => array (
// 'requests' => true,
// 'responses' => true
//),
// // Security settings
// 'security' => array (
//
// /** signatures and encryptions offered */
//
// // Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
// // will be encrypted.
// 'nameIdEncrypted' => false,
//
// // Indicates whether the <samlp:AuthnRequest> messages sent by this SP
// // will be signed. [Metadata of the SP will offer this info]
// 'authnRequestsSigned' => false,
//
// // Indicates whether the <samlp:logoutRequest> messages sent by this SP
// // will be signed.
// 'logoutRequestSigned' => false,
//
// // Indicates whether the <samlp:logoutResponse> messages sent by this SP
// // will be signed.
// 'logoutResponseSigned' => false,
//
// /* Sign the Metadata
// False || True (use sp certs) || array (
// keyFileName => 'metadata.key',
// certFileName => 'metadata.crt'
// )
// */
// 'signMetadata' => false,
//
// /** signatures and encryptions required **/
//
// // Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest>
// // and <samlp:LogoutResponse> elements received by this SP to be signed.
// 'wantMessagesSigned' => false,
//
// // Indicates a requirement for the <saml:Assertion> elements received by
// // this SP to be encrypted.
// 'wantAssertionsEncrypted' => false,
//
// // Indicates a requirement for the <saml:Assertion> elements received by
// // this SP to be signed. [Metadata of the SP will offer this info]
// 'wantAssertionsSigned' => false,
//
// // Indicates a requirement for the NameID element on the SAMLResponse
// // received by this SP to be present.
// 'wantNameId' => true,
//
// // Indicates a requirement for the NameID received by
// // this SP to be encrypted.
// 'wantNameIdEncrypted' => false,
//
// // Authentication context.
// // Set to false and no AuthContext will be sent in the AuthNRequest.
// // Set true or don't present this parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'.
// // Set an array with the possible auth context values: array ('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509').
// 'requestedAuthnContext' => true,
//
// // Indicates if the SP will validate all received xmls.
// // (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true).
// 'wantXMLValidation' => true,
//
// // If true, SAMLResponses with an empty value at its Destination
// // attribute will not be rejected for this fact.
// 'relaxDestinationValidation' => false,
//
// // Algorithm that the toolkit will use on signing process. Options:
// // 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
// // 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'
// // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
// // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
// // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
// // Notice that sha1 is a deprecated algorithm and should not be used
// 'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
//
// // Algorithm that the toolkit will use on digest process. Options:
// // 'http://www.w3.org/2000/09/xmldsig#sha1'
// // 'http://www.w3.org/2001/04/xmlenc#sha256'
// // 'http://www.w3.org/2001/04/xmldsig-more#sha384'
// // 'http://www.w3.org/2001/04/xmlenc#sha512'
// // Notice that sha1 is a deprecated algorithm and should not be used
// 'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
//
// // ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses
// // uppercase. Turn it True for ADFS compatibility on signature verification
// 'lowercaseUrlencoding' => false,
//),
//
// // Contact information template, it is recommended to supply a
// // technical and support contacts.
// 'contactPerson' => array (
// 'technical' => array (
// 'givenName' => 'example',
// 'emailAddress' => 'test@example.org'
// ),
// 'support' => array (
// 'givenName' => 'example',
// 'emailAddress' => 'test@example.org'
// ),
//),
//
// // Organization information template, the info in en_US lang is
// // recomended, add more if required.
// 'organization' => array (
// 'en-US' => array(
// 'name' => 'chamilo',
// 'displayname' => 'chamilo',
// 'url' => 'chamilo.org'
// ),
//),

@ -0,0 +1,192 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Settings;
use OneLogin\Saml2\AuthnRequest;
require_once '../../main/inc/global.inc.php';
$pluginKeycloak = api_get_plugin_setting('keycloak', 'tool_enable') === 'true';
if (!$pluginKeycloak) {
api_not_allowed(true);
}
// Create a settings.dist.php
require_once 'settings.php';
$content = '';
$auth = new Auth($settingsInfo);
if (isset($_REQUEST['delete'])) {
Session::erase('samlNameId');
Session::erase('samlSessionIndex');
Session::erase('samlNameIdFormat');
Session::erase('samlUserdata');
Session::erase('AuthNRequestID');
Session::erase('LogoutRequestID');
echo 'delete all';
exit;
}
$settings = new Settings($settingsInfo);
$authRequest = new AuthnRequest($settings);
$samlRequest = $authRequest->getRequest();
$idpData = $settings->getIdPData();
if (isset($_GET['sso'])) {
$auth->login();
# If AuthNRequest ID need to be saved in order to later validate it, do instead
/*$ssoBuiltUrl = $auth->login(null, [], false, false, true);
$_SESSION['AuthNRequestID'] = $auth->getLastRequestID();
header('Pragma: no-cache');
header('Cache-Control: no-cache, must-revalidate');
header('Location: ' . $ssoBuiltUrl);
exit();*/
} else if (isset($_GET['slo'])) {
/*
if (isset($idpData['singleLogoutService']) && isset($idpData['singleLogoutService']['url'])) {
$sloUrl = $idpData['singleLogoutService']['url'];
} else {
throw new Exception("The IdP does not support Single Log Out");
}
if (isset($_SESSION['samlSessionIndex']) && !empty($_SESSION['samlSessionIndex'])) {
$logoutRequest = new \OneLogin\Saml2\LogoutRequest($settings, null, $_SESSION['samlSessionIndex']);
} else {
$logoutRequest = new \OneLogin\Saml2\LogoutRequest($settings);
}
$samlRequest = $logoutRequest->getRequest();
$parameters = array('SAMLRequest' => $samlRequest);
$url = \OneLogin\Saml2\Utils::redirect($sloUrl, $parameters, true);
header("Location: $url");
exit;*/
$returnTo = null;
$parameters = [];
$nameId = Session::read('samlNameId');
$sessionIndex = Session::read('samlSessionIndex');
$nameIdFormat = Session::read('samlNameIdFormat');
$auth->logout($returnTo, $parameters, $nameId, $sessionIndex, false, $nameIdFormat);
# If LogoutRequest ID need to be saved in order to later validate it, do instead
// $sloBuiltUrl = $auth->logout(null, [], $nameId, $sessionIndex, true);
/*$_SESSION['LogoutRequestID'] = $auth->getLastRequestID();
header('Pragma: no-cache');
header('Cache-Control: no-cache, must-revalidate');
header('Location: ' . $sloBuiltUrl);
exit();*/
} else if (isset($_GET['acs'])) {
$requestID = Session::read('AuthNRequestID');
$auth->processResponse($requestID);
$errors = $auth->getErrors();
if (!empty($errors)) {
$content .= '<p>'.implode(', ', $errors).'</p>';
}
if (!$auth->isAuthenticated()) {
api_not_allowed(true, $content.'<p>Not authenticated</p>');
exit;
}
// Set chamilo sessions
Session::write('samlUserdata', $auth->getAttributes());
Session::write('samlNameId', $auth->getNameId());
Session::write('samlNameIdFormat', $auth->getNameIdFormat());
Session::write('samlSessionIndex', $auth->getSessionIndex());
Session::erase('AuthNRequestID');
$keyCloackUserName = Session::read('samlNameId');
$userInfo = api_get_user_info_from_username($keyCloackUserName);
if (empty($userInfo)) {
$firstName = reset($attributes['FirstName']);
$lastName = reset($attributes['LastName']);
$email = reset($attributes['Email']);
$userId = UserManager::create_user(
$firstName,
$lastName,
STUDENT,
$email,
$keyCloackUserName,
'',
'',
'',
'',
'',
'keycloak'
);
} else {
$userId = $userInfo['user_id'];
}
if (!empty($userId)) {
// Filling session variables with new data
Session::write('_uid', $userId);
Session::write('_user', $userInfo);
Session::write('is_platformAdmin', false);
Session::write('is_allowedCreateCourse', false);
}
if (isset($_POST['RelayState']) && \OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) {
//$auth->redirectTo($_POST['RelayState']);
}
header('Location: '.api_get_path(WEB_PATH));
exit;
} else if (isset($_GET['sls'])) {
$requestID = Session::read('LogoutRequestID');
$auth->processSLO(false, $requestID);
$errors = $auth->getErrors();
if (empty($errors)) {
Session::erase('samlNameId');
Session::erase('samlSessionIndex');
Session::erase('samlNameIdFormat');
Session::erase('samlUserdata');
Session::erase('AuthNRequestID');
Session::erase('LogoutRequestID');
Display::addFlash(Display::return_message('Sucessfully logged out'));
header('Location: '.api_get_path(WEB_PATH));
exit;
} else {
api_not_allowed(true, implode(', ', $errors));
}
}
$template = new Template('');
if (isset($_SESSION['samlUserdata'])) {
$attributes = Session::read('samlUserdata');
$params = [];
if (!empty($attributes)) {
$content .= 'You have the following attributes:<br>';
$content .= '<table class="table"><thead><th>Name</th><th>Values</th></thead><tbody>';
foreach ($attributes as $attributeName => $attributeValues) {
$content .= '<tr><td>' . htmlentities($attributeName) . '</td><td><ul>';
foreach ($attributeValues as $attributeValue) {
$content .= '<li>' . htmlentities($attributeValue) . '</li>';
}
$content .= '</ul></td></tr>';
}
$content .= '</tbody></table>';
} else {
$content .= "<p>You don't have any attribute</p>";
}
$content .= '<p><a href="?slo" >Logout</a></p>';
} else {
$content .= '<p><a href="?sso" >Login</a></p>';
$content .= '<p><a href="?sso2" >Login and access to attrs.php page</a></p>';
}
$template->assign('content', $content);
$template->display_one_col_template();
Loading…
Cancel
Save