parent
db5b1d84e8
commit
83031035d0
@ -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…
Reference in new issue