Skip to content
This repository has been archived by the owner on Dec 27, 2023. It is now read-only.

Commit

Permalink
feature(Tinebase): sso ui for mfa
Browse files Browse the repository at this point in the history
  • Loading branch information
corneliusweiss authored and paulmhh committed Aug 5, 2021
1 parent 2e6e8e9 commit 1ec9c35
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 31 deletions.
61 changes: 35 additions & 26 deletions tine20/SSO/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -513,21 +513,24 @@ public static function publicSaml2RedirectRequest(): \Psr\Http\Message\ResponseI

throw new Tinebase_Exception('expect simplesaml to throw a resolution');
} catch (SSO_Facade_SAML_MFAMaskException $e) {
// render MFA mask
$response = (new \Zend\Diactoros\Response())->withHeader('content-type', 'application/json');
$response->getBody()->write(json_encode([
'jsonrpc' => '2.0',
'id' => 'fakeid',
'error' => [
'code' => -32000,
'message' => $e->mfaException->getMessage(),
'data' => $e->mfaException->toArray(),
],
]));
if (array_key_exists('username', $request->getParsedBody())) {
// render MFA mask
$response = (new \Zend\Diactoros\Response())->withHeader('content-type', 'application/json');
$response->getBody()->write(json_encode([
'jsonrpc' => '2.0',
'id' => 'fakeid',
'error' => [
'code' => -32000,
'message' => $e->mfaException->getMessage(),
'data' => $e->mfaException->toArray(),
],
]));
} else {
// reload while in mfa required state
$response = self::getLoginPage($request);
}
} catch (SSO_Facade_SAML_LoginMaskException $e) {
$data = $request->getQueryParams();

if (array_key_exists('username', $data)) {
if (array_key_exists('username', $request->getParsedBody())) {
// this is our js client trying to login
$response = (new \Zend\Diactoros\Response())->withHeader('content-type', 'application/json');
$response->getBody()->write(json_encode([
Expand All @@ -536,18 +539,7 @@ public static function publicSaml2RedirectRequest(): \Psr\Http\Message\ResponseI
'result' => (new Tinebase_Frontend_Json())->_getLoginFailedResponse(),
]));
} else {
$data['SAMLRequest'] = base64_encode(gzinflate(base64_decode($data['SAMLRequest'])));

$locale = Tinebase_Core::getLocale();

$jsFiles = ['SSO/js/login.js'];
$jsFiles[] = "index.php?method=Tinebase.getJsTranslations&locale={$locale}&app=all";

$response = Tinebase_Frontend_Http_SinglePageApplication::getClientHTML($jsFiles, 'Tinebase/views/singlePageApplication.html.twig', [
'base' => Tinebase_Core::getUrl(Tinebase_Core::GET_URL_PATH),
'lang' => $locale,
'initialData' => json_encode(['sso' => $data])
]);
$response = self::getLoginPage($request);
}
} catch (SSO_Facade_SAML_RedirectException $e) {
$response = new \Zend\Diactoros\Response();
Expand Down Expand Up @@ -580,6 +572,23 @@ public static function publicSaml2RedirectRequest(): \Psr\Http\Message\ResponseI
return $response;
}

protected static function getLoginPage($request)
{
$data = $request->getQueryParams();
$data['SAMLRequest'] = base64_encode(gzinflate(base64_decode($data['SAMLRequest'])));

$locale = Tinebase_Core::getLocale();

$jsFiles = ['SSO/js/login.js'];
$jsFiles[] = "index.php?method=Tinebase.getJsTranslations&locale={$locale}&app=all";

return Tinebase_Frontend_Http_SinglePageApplication::getClientHTML($jsFiles, 'Tinebase/views/singlePageApplication.html.twig', [
'base' => Tinebase_Core::getUrl(Tinebase_Core::GET_URL_PATH),
'lang' => $locale,
'initialData' => json_encode(['sso' => $data])
]);
}

protected static function getOpenIdConnectServer(): \League\OAuth2\Server\AuthorizationServer
{
// Setup the authorization server
Expand Down
4 changes: 2 additions & 2 deletions tine20/SSO/Facade/SAML/AuthSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public function authenticate(&$state)
$accessLog = new Tinebase_Model_AccessLog(['clienttype' => Tinebase_Frontend_Json::REQUEST_TYPE], true);
Tinebase_Controller::getInstance()->forceUnlockLoginArea();
Tinebase_Controller::getInstance()->setRequestContext(array(
'MFAPassword' => isset($request->getQueryParams()['MFAPassword']) ? $request->getQueryParams()['MFAPassword'] : null,
'MFAId' => isset($request->getQueryParams()['MFAUserConfigId']) ? $request->getQueryParams()['MFAUserConfigId'] : null,
'MFAPassword' => isset($request->getParsedBody()['MFAPassword']) ? $request->getParsedBody()['MFAPassword'] : null,
'MFAId' => isset($request->getParsedBody()['MFAUserConfigId']) ? $request->getParsedBody()['MFAUserConfigId'] : null,
));
try {
Tinebase_Controller::getInstance()->_validateSecondFactor($accessLog, $user);
Expand Down
27 changes: 24 additions & 3 deletions tine20/SSO/js/login.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/*
* Tine 2.0
*
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Cornelius Weiss <c.weiss@metaways.de>
* @copyright Copyright (c) 2021 Metaways Infosystems GmbH (http://www.metaways.de)
*/

/**
* entry point for sso login client
*/
import(/* webpackChunkName: "Tinebase/js/Tinebase" */ 'Tinebase.js').then(function (libs) {
libs.lodash.assign(window, libs);
require('tineInit');
Expand All @@ -6,9 +17,12 @@ import(/* webpackChunkName: "Tinebase/js/Tinebase" */ 'Tinebase.js').then(functi
var mainCardPanel = Tine.Tinebase.viewport.tineViewportMaincardpanel;

Tine.loginPanel = new Tine.Tinebase.LoginPanel({
defaultUsername: Tine.Tinebase.registry.get('defaultUsername'),
defaultPassword: Tine.Tinebase.registry.get('defaultPassword'),
allowBrowserPasswordManager: Tine.Tinebase.registry.get('allowBrowserPasswordManager'),
headsUpText: window.i18n._('SSO'),
scope: this,
onLoginPress: function () {
onLoginPress: function (additionalParams) {
Ext.MessageBox.wait(i18n._('Logging you in...'), i18n._('Please wait'));

const form = this.getLoginPanel().getForm();
Expand All @@ -17,14 +31,21 @@ import(/* webpackChunkName: "Tinebase/js/Tinebase" */ 'Tinebase.js').then(functi
formData.append('username', values.username);
formData.append('password', values.password);
Object.keys(window.initialData.sso).forEach((key) => {formData.append(key, window.initialData.sso[key])});
Object.keys(additionalParams || {}).forEach((key) => {formData.append(key, additionalParams[key])});

var xhr = new XMLHttpRequest();
xhr.addEventListener("load", () => {
if (xhr.status >= 200 && xhr.status < 300) {
const isJSON = xhr.responseText.match(/^{/);
if (xhr.status >= 200 && xhr.status < 300 && !isJSON) {
window.document.body.innerHTML = xhr.responseText;
document.getElementsByTagName("form")[0].submit();
} else {
//@TODO MFA
if (isJSON) {
const response = JSON.parse(xhr.responseText);
if (response?.error?.data) {
return this.onLoginFail({responseText: JSON.stringify(response.error)});
}
}
Ext.MessageBox.show({
title: i18n._('Login failure'),
msg: i18n._('Your username and/or your password are wrong!'),
Expand Down
9 changes: 9 additions & 0 deletions tine20/SSO/js/sso.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Tine 2.0
*
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Cornelius Weiss <c.weiss@metaways.de>
* @copyright Copyright (c) 2021 Metaways Infosystems GmbH (http://www.metaways.de)
*/

// sso app entry point for tine20 fat-client

0 comments on commit 1ec9c35

Please sign in to comment.