From b11e5bddeaa02400083d69e6581e19539b771a33 Mon Sep 17 00:00:00 2001 From: lucs7 Date: Sat, 8 Feb 2025 13:06:07 +0100 Subject: [PATCH 1/3] small typo in entrypoint.sh --- entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index 371409a8a..56b991834 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -8,7 +8,7 @@ if ! [ -f config/config.php ]; then \ echo '$conf["settings"]["database"]["hostspec"] = ' "'$DB_HOST';" >>config/config.php; \ echo '$conf["settings"]["database"]["name"] = ' "'$DB_DATABASE';" >>config/config.php; \ echo '$conf["settings"]["install.password"] = ' "'$INSTALL_PASSWORD';" >>config/config.php; \ - echo '$conf["settings"]["sript.url"] = ' "'$SCRIPT_URL';" >>config/config.php; \ + echo '$conf["settings"]["script.url"] = ' "'$SCRIPT_URL';" >>config/config.php; \ echo '$conf["settings"]["admin.email"] = ' "'$ADMIN_EMAIL';" >>config/config.php; \ fi From df120e71ad3ae8e3bef705eb2aed90345f35976a Mon Sep 17 00:00:00 2001 From: lucs7 Date: Sat, 8 Feb 2025 13:06:37 +0100 Subject: [PATCH 2/3] added keycloak as external auth provider --- Pages/LoginPage.php | 57 ++-- .../ExternalAuthLoginPresenter.php | 155 ++++++--- Presenters/LoginPresenter.php | 76 +++-- Web/keycloak-auth.php | 14 + config/config.dist.php | 8 + lib/Config/ConfigKeys.php | 6 + tpl/login.tpl | 295 +++++++++--------- 7 files changed, 375 insertions(+), 236 deletions(-) create mode 100644 Web/keycloak-auth.php diff --git a/Pages/LoginPage.php b/Pages/LoginPage.php index 854a78646..e3df15d58 100644 --- a/Pages/LoginPage.php +++ b/Pages/LoginPage.php @@ -94,19 +94,24 @@ public function GetCaptcha(); public function SetAnnouncements($announcements); /** - * + * */ public function SetGoogleUrl($URL); /** - * + * */ public function SetMicrosoftUrl($URL); /** - * + * */ public function SetFacebookUrl($URL); + + /** + * + */ + public function SetKeycloakUrl($URL); } class LoginPage extends Page implements ILoginPage @@ -119,7 +124,7 @@ public function __construct() $this->presenter = new LoginPresenter($this); // $this pseudo variable of class object is Page object $resumeUrl = $this->server->GetQuerystring(QueryStringKeys::REDIRECT); - if($resumeUrl !== NULL) $resumeUrl = str_replace('&&', '&', $resumeUrl); + if ($resumeUrl !== NULL) $resumeUrl = str_replace('&&', '&', $resumeUrl); $this->Set('ResumeUrl', $resumeUrl); $this->Set('ShowLoginError', false); $this->Set('Languages', Resources::GetInstance()->AvailableLanguages); @@ -128,11 +133,12 @@ public function __construct() $this->Set('AllowFacebookLogin', Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_FACEBOOK, new BooleanConverter())); $this->Set('AllowGoogleLogin', Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_GOOGLE, new BooleanConverter())); $this->Set('AllowMicrosoftLogin', Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_MICROSOFT, new BooleanConverter())); + $this->Set('AllowKeycloakLogin', Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_KEYCLOAK, new BooleanConverter())); $scriptUrl = Configuration::Instance()->GetScriptUrl(); $parts = explode('://', $scriptUrl); $this->Set('Protocol', $parts[0]); if (isset($parts[1])) { - $this->Set('ScriptUrlNoProtocol', $parts[1]); + $this->Set('ScriptUrlNoProtocol', $parts[1]); } $this->Set('GoogleState', strtr(base64_encode("resume=$scriptUrl/external-auth.php%3Ftype%3Dgoogle%26redirect%3D$resumeUrl"), '+/=', '-_,')); $this->Set('EnableCaptcha', Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_CAPTCHA_ON_LOGIN, new BooleanConverter())); @@ -299,41 +305,52 @@ public function SetAnnouncements($announcements) } /** - * Sends the created google url in the presenter to the smarty page + * Sends the created google url in the presenter to the smarty page */ - public function SetGoogleUrl($googleUrl){ - if(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_GOOGLE, new BooleanConverter())){ - $this->Set('GoogleUrl',$googleUrl); + public function SetGoogleUrl($googleUrl) + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_GOOGLE, new BooleanConverter())) { + $this->Set('GoogleUrl', $googleUrl); } } /** - * Sends the created microsoft url in the presenter to the smarty page + * Sends the created microsoft url in the presenter to the smarty page */ - public function SetMicrosoftUrl($microsoftUrl){ - if(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_MICROSOFT, new BooleanConverter())){ - $this->Set('MicrosoftUrl',$microsoftUrl); + public function SetMicrosoftUrl($microsoftUrl) + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_MICROSOFT, new BooleanConverter())) { + $this->Set('MicrosoftUrl', $microsoftUrl); } } /** - * Sends the created facebook url in the presenter to the smarty page + * Sends the created facebook url in the presenter to the smarty page */ - public function SetFacebookUrl($FacebookUrl){ - if(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_FACEBOOK, new BooleanConverter())){ - $this->Set('FacebookUrl',$FacebookUrl); + public function SetFacebookUrl($FacebookUrl) + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_FACEBOOK, new BooleanConverter())) { + $this->Set('FacebookUrl', $FacebookUrl); } } /** - * Temporary solution for facebook auth SDK error + * Temporary solution for facebook auth SDK error * After facebook failed authentication user is redirected to login page (this one) and is shown a message to try again * Error occurs rarely (FacebookSDKException) */ - private function SetFacebookErrorMessage(){ + private function SetFacebookErrorMessage() + { if (isset($_SESSION['facebook_error']) && $_SESSION['facebook_error'] == true) { - $this->Set('facebookError',$_SESSION['facebook_error']); + $this->Set('facebookError', $_SESSION['facebook_error']); unset($_SESSION['facebook_error']); } } + + public function SetKeycloakUrl($KeycloakUrl) + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_KEYCLOAK, new BooleanConverter())) { + $this->Set('KeycloakUrl', $KeycloakUrl); + } + } } diff --git a/Presenters/Authentication/ExternalAuthLoginPresenter.php b/Presenters/Authentication/ExternalAuthLoginPresenter.php index b57e3ee8c..abaa24394 100644 --- a/Presenters/Authentication/ExternalAuthLoginPresenter.php +++ b/Presenters/Authentication/ExternalAuthLoginPresenter.php @@ -35,6 +35,9 @@ public function PageLoad() if ($this->page->GetType() == 'microsoft') { $this->ProcessMicrosoftSingleSignOn(); } + if ($this->page->GetType() == 'keycloak') { + $this->ProcessKeycloakSingleSignOn(); + } } /** @@ -54,18 +57,18 @@ private function ProcessGoogleSingleSignOn() $token = $client->fetchAccessTokenWithAuthCode($_GET['code']); //set the access token that it received $client->setAccessToken($token['access_token']); - - //Using the Google API to get the user information + + //Using the Google API to get the user information $google_oauth = new Google\Service\Oauth2($client); $google_account_info = $google_oauth->userinfo->get(); - + //Save the informations needed to authenticate the login $email = $google_account_info->email; $firstName = $google_account_info->given_name; $lastName = $google_account_info->family_name; - + //Process $userData as needed (e.g., create a user, log in, etc.) - $this->processUserData($email,$email,$firstName,$lastName); + $this->processUserData($email, $email, $firstName, $lastName); } } @@ -73,13 +76,13 @@ private function ProcessGoogleSingleSignOn() * Exchanges the code given by microsoft in _GET for a token and with said token retrieves client data */ private function ProcessMicrosoftSingleSignOn() - { + { if (isset($_GET['code'])) { $code = filter_input(INPUT_GET, 'code'); $tokenEndpoint = 'https://login.microsoftonline.com/' - .urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_TENANT_ID)) - .'/oauth2/v2.0/token'; + . urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_TENANT_ID)) + . '/oauth2/v2.0/token'; $postData = [ 'client_id' => Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_CLIENT_ID), @@ -95,13 +98,13 @@ private function ProcessMicrosoftSingleSignOn() $response = $client->post($tokenEndpoint, [ 'form_params' => $postData, ]); - + // Decode the JSON response $tokenData = json_decode($response->getBody(), true); // Extract the access token from the response $accessToken = $tokenData['access_token']; - + //Get user information $graphApiEndpoint = 'https://graph.microsoft.com/v1.0/me'; @@ -111,80 +114,154 @@ private function ProcessMicrosoftSingleSignOn() 'Authorization' => 'Bearer ' . $accessToken, ], ]); - + // Decode the JSON response $userData = json_decode($response->getBody(), true); - + // Handle the user data as needed $email = $userData['mail']; $firstName = $userData['givenName'];; $lastName = $userData['surname']; - + //Process $userData as needed (e.g., create a user, log in, etc.) - $this->processUserData($email,$email,$firstName,$lastName); + $this->processUserData($email, $email, $firstName, $lastName); } - } /** * Gets token created in facebook-auth.php and exchanges it for the client data * Unlike the other two (microsoft and google) the token must be obtained directly in the redirect uri, therefore can't be sent here for exchange(?) */ - private function ProcessFacebookSingleSignOn(){ - + private function ProcessFacebookSingleSignOn() + { + $facebook_Client = new Facebook\Facebook([ 'app_id' => Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::FACEBOOK_CLIENT_ID), 'app_secret' => Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::FACEBOOK_CLIENT_SECRET), 'default_graph_version' => 'v2.5' ]); - + if (isset($_SESSION['facebook_access_token'])) { $facebook_Client->setDefaultAccessToken(unserialize($_SESSION['facebook_access_token'])); } unset($_SESSION['facebook_access_token']); - $profile_request = $facebook_Client ->get('/me?fields=name,first_name,last_name,email'); - $profile = $profile_request ->getGraphUser(); + $profile_request = $facebook_Client->get('/me?fields=name,first_name,last_name,email'); + $profile = $profile_request->getGraphUser(); $email = $profile->getField('email'); $firstName = $profile->getField('first_name'); $lastName = $profile->getField('last_name'); //Process $userData as needed (e.g., create a user, log in, etc.) - $this->processUserData($email,$email,$firstName,$lastName); - + $this->processUserData($email, $email, $firstName, $lastName); + } + + private function ProcessKeycloakSingleSignOn() + { + $code = $_GET['code']; + + $keycloakUrl = Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_URL); + $realm = Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_REALM); + $clientId = Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_CLIENT_ID); + $clientSecret = Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_CLIENT_SECRET); + $redirectUri = rtrim(Configuration::Instance()->GetScriptUrl(), 'Web/') . Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_REDIRECT_URI); + + $tokenEndpoint = rtrim($keycloakUrl, '/') . '/realms/' . urlencode($realm) . '/protocol/openid-connect/token'; + + // Prepare the POST data for the token request. + $postData = [ + 'grant_type' => 'authorization_code', + 'code' => $code, + 'redirect_uri' => $redirectUri, + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + ]; + + $client = new \GuzzleHttp\Client(); + + try { + $response = $client->post($tokenEndpoint, [ + 'form_params' => $postData, + ]); + } catch (\Exception $e) { + $this->page->ShowError(['Error retrieving Keycloak token: ' . $e->getMessage()]); + return; + } + + $tokenData = json_decode($response->getBody(), true); + if (!isset($tokenData['access_token'])) { + $this->page->ShowError(['Access token not found in Keycloak response']); + return; + } + $accessToken = $tokenData['access_token']; + + // Build the userinfo endpoint URL (again, without '/auth'). + $userInfoEndpoint = rtrim($keycloakUrl, '/') . '/realms/' . urlencode($realm) . '/protocol/openid-connect/userinfo'; + + try { + $userResponse = $client->get($userInfoEndpoint, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $accessToken, + ], + ]); + } catch (\Exception $e) { + $this->page->ShowError(['Error retrieving Keycloak user info: ' . $e->getMessage()]); + return; + } + + $userData = json_decode($userResponse->getBody(), true); + + $email = isset($userData['email']) ? $userData['email'] : ''; + $firstName = isset($userData['given_name']) ? $userData['given_name'] : 'not set'; + $lastName = isset($userData['family_name']) ? $userData['family_name'] : 'not set'; + $username = isset($userData['preferred_username']) ? $userData['preferred_username'] : $email; + $phone = isset($userData['phone_number']) ? $userData['phone_number'] : ''; + $organization = isset($userData['organization']) ? $userData['organization'] : ''; + $title = isset($userData['title']) ? $userData['title'] : ''; + + if (empty($email)) { + $this->page->ShowError(["Email is not set in your Keycloak profile. Please update your profile and try again."]); + return; + } + + $this->processUserData($username, $email, $firstName, $lastName, $phone, $organization, $title); } + /** * Processes user given data, creates a user in database if it doesn't exist and logs it in */ - private function processUserData($username,$email,$firstName,$lastName){ + private function processUserData($username, $email, $firstName, $lastName, $phone = null, $organization = null, $title = null) + { $requiredDomainValidator = new RequiredEmailDomainValidator($email); $requiredDomainValidator->Validate(); if (!$requiredDomainValidator->IsValid()) { $this->page->ShowError(array(Resources::GetInstance()->GetString('InvalidEmailDomain'))); return; } - if($this->registration->UserExists($username,$email)){ + if ($this->registration->UserExists($username, $email)) { $this->authentication->Login($email, new WebLoginContext(new LoginData())); LoginRedirector::Redirect($this->page); - } - else{ - $this->registration->Synchronize(new AuthenticatedUser( - $username, - $email, - $firstName, - $lastName, - Password::GenerateRandom(), - Resources::GetInstance()->CurrentLanguage, - Configuration::Instance()->GetDefaultTimezone(), - null, - null, - null), + } else { + $this->registration->Synchronize( + new AuthenticatedUser( + $username, + $email, + $firstName, + $lastName, + Password::GenerateRandom(), + Resources::GetInstance()->CurrentLanguage, + Configuration::Instance()->GetDefaultTimezone(), + $phone, + $organization, + $title + ), false, - false); + false + ); $this->authentication->Login($email, new WebLoginContext(new LoginData())); LoginRedirector::Redirect($this->page); } } -} \ No newline at end of file +} diff --git a/Presenters/LoginPresenter.php b/Presenters/LoginPresenter.php index e580bc2c4..ee60a80b6 100644 --- a/Presenters/LoginPresenter.php +++ b/Presenters/LoginPresenter.php @@ -109,11 +109,11 @@ public function PageLoad() $this->_page->SetShowScheduleLink($allowAnonymousSchedule || $allowGuestBookings); $hideLogin = Configuration::Instance() - ->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_HIDE_BOOKED_LOGIN_PROMPT, new BooleanConverter()); + ->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_HIDE_BOOKED_LOGIN_PROMPT, new BooleanConverter()); $this->_page->ShowForgotPasswordPrompt(!Configuration::Instance()->GetKey(ConfigKeys::DISABLE_PASSWORD_RESET, new BooleanConverter()) && - $this->authentication->ShowForgotPasswordPrompt() && - !$hideLogin); + $this->authentication->ShowForgotPasswordPrompt() && + !$hideLogin); $this->_page->ShowPasswordPrompt($this->authentication->ShowPasswordPrompt() && !$hideLogin); $this->_page->ShowPersistLoginPrompt($this->authentication->ShowPersistLoginPrompt()); @@ -126,6 +126,7 @@ public function PageLoad() $this->_page->SetGoogleUrl($this->GetGoogleUrl()); $this->_page->SetMicrosoftUrl($this->GetMicrosoftUrl()); $this->_page->SetFacebookUrl($this->GetFacebookUrl()); + $this->_page->SetKeycloakUrl($this->GetKeycloakUrl()); } public function Login() @@ -227,10 +228,11 @@ protected function LoadValidators() /** * Checks in the config files if google authentication is active creating a new client if true and setting it's config keys. - * Returns the created google url for the authentication + * Returns the created google url for the authentication */ - public function GetGoogleUrl(){ - if(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_GOOGLE, new BooleanConverter())){ + public function GetGoogleUrl() + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_GOOGLE, new BooleanConverter())) { $client = new Google\Client(); $client->setClientId(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::GOOGLE_CLIENT_ID)); $client->setClientSecret(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::GOOGLE_CLIENT_SECRET)); @@ -239,25 +241,26 @@ public function GetGoogleUrl(){ $client->addScope("profile"); $client->setPrompt("select_account"); $GoogleUrl = $client->createAuthUrl(); - + return $GoogleUrl; } } /** * Checks in the config files if microsoft authentication is active creating the url if true with the respective keys - * Returns the created microsoft url for the authentication + * Returns the created microsoft url for the authentication */ - public function GetMicrosoftUrl(){ - if(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_MICROSOFT, new BooleanConverter())){ + public function GetMicrosoftUrl() + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_MICROSOFT, new BooleanConverter())) { $MicrosoftUrl = 'https://login.microsoftonline.com/' - .urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_TENANT_ID)) - .'/oauth2/v2.0/authorize?' - .'client_id=' . urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_CLIENT_ID)) - .'&redirect_uri=' . urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_REDIRECT_URI)) - .'&scope=user.read' - .'&response_type=code' - .'&prompt=select_account'; + . urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_TENANT_ID)) + . '/oauth2/v2.0/authorize?' + . 'client_id=' . urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_CLIENT_ID)) + . '&redirect_uri=' . urlencode(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::MICROSOFT_REDIRECT_URI)) + . '&scope=user.read' + . '&response_type=code' + . '&prompt=select_account'; return $MicrosoftUrl; } @@ -265,21 +268,22 @@ public function GetMicrosoftUrl(){ /** * Checks in the config files if facebook authentication is active creating the url if true with the respective keys - * Returns the created facebook url for the authentication + * Returns the created facebook url for the authentication */ - public function GetFacebookUrl(){ - if(Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_FACEBOOK, new BooleanConverter())){ + public function GetFacebookUrl() + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_FACEBOOK, new BooleanConverter())) { $facebook_Client = new Facebook\Facebook([ - 'app_id' => Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::FACEBOOK_CLIENT_ID), - 'app_secret' => Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::FACEBOOK_CLIENT_SECRET), - 'default_graph_version' => 'v2.5' + 'app_id' => Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::FACEBOOK_CLIENT_ID), + 'app_secret' => Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::FACEBOOK_CLIENT_SECRET), + 'default_graph_version' => 'v2.5' ]); $helper = $facebook_Client->getRedirectLoginHelper(); $permissions = ['email', 'public_profile']; // Add other permissions as needed - - //The FacebookRedirectLoginHelper makes use of sessions to store a CSRF value. + + //The FacebookRedirectLoginHelper makes use of sessions to store a CSRF value. //You need to make sure you have sessions enabled before invoking the getLoginUrl() method. if (!session_id()) { session_start(); @@ -292,4 +296,26 @@ public function GetFacebookUrl(){ return $FacebookUrl; } } + + public function GetKeycloakUrl() + { + if (Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::AUTHENTICATION_ALLOW_KEYCLOAK, new BooleanConverter())) { + // Retrieve Keycloak configuration values + $baseUrl = Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_URL); + $realm = Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_REALM); + $clientId = Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_CLIENT_ID); + $redirectUri = rtrim(Configuration::Instance()->GetScriptUrl(), 'Web/') . Configuration::Instance()->GetSectionKey(ConfigSection::AUTHENTICATION, ConfigKeys::KEYCLOAK_REDIRECT_URI); + + // Construct the Keycloak authentication URL + $keycloakUrl = rtrim($baseUrl, '/') + . '/realms/' . urlencode($realm) + . '/protocol/openid-connect/auth?' + . 'client_id=' . urlencode($clientId) + . '&redirect_uri=' . urlencode($redirectUri) + . '&response_type=code' + . '&scope=' . urlencode('openid email profile'); + + return $keycloakUrl; + } + } } diff --git a/Web/keycloak-auth.php b/Web/keycloak-auth.php new file mode 100644 index 000000000..951a3ef19 --- /dev/null +++ b/Web/keycloak-auth.php @@ -0,0 +1,14 @@ + - {if $EnableCaptcha} - {validation_group class="alert alert-danger"} - {validator id="captcha" key="CaptchaMustMatch"} - {/validation_group} - {/if} - - {if $Announcements|default:array()|count > 0} -
-
- {foreach from=$Announcements item=each} -
{$each->Text()|html_entity_decode|url2link|nl2br}
- {/foreach} -
-
- {/if} - -
-
-
-
-
- - - {if $ShowLoginError} -
- {translate key='LoginError'} -
- {/if} - - {if $ShowUsernamePrompt} -
- - -
- {/if} - - {if $ShowPasswordPrompt} -
- - -
- {/if} - - {if $EnableCaptcha} -
- {control type="CaptchaControl"} -
- {else} - - {/if} - - {if $ShowUsernamePrompt && $ShowPasswordPrompt} -
- - -
- {/if} - -
- {if $ShowUsernamePrompt && $ShowPasswordPrompt} -
-
- - -
-
- {/if} - - {if $ShowRegisterLink} -
- {translate key="FirstTimeUser?"} - {translate key=Register} - -
- {/if} -
- - - {if $facebookError} -

- {translate key="FacebookLoginErrorMessage"}

- {/if} -
-
- -
- -
-
+ {if $EnableCaptcha} + {validation_group class="alert alert-danger"} + {validator id="captcha" key="CaptchaMustMatch"} + {/validation_group} + {/if} + + {if $Announcements|default:array()|count > 0} +
+
+ {foreach from=$Announcements item=each} +
{$each->Text()|html_entity_decode|url2link|nl2br}
+ {/foreach} +
+
+ {/if} + +
+
+
+
+
+ + + {if $ShowLoginError} +
+ {translate key='LoginError'} +
+ {/if} + + {if $ShowUsernamePrompt} +
+ + +
+ {/if} + + {if $ShowPasswordPrompt} +
+ + +
+ {/if} + + {if $EnableCaptcha} +
+ {control type="CaptchaControl"} +
+ {else} + + {/if} + + {if $ShowUsernamePrompt && $ShowPasswordPrompt} +
+ + +
+ {/if} + +
+ {if $ShowUsernamePrompt && $ShowPasswordPrompt} +
+
+ + +
+
+ {/if} + + {if $ShowRegisterLink} +
+ {translate key="FirstTimeUser?"} + {translate key=Register} + +
+ {/if} +
+ + + {if $facebookError} +

+ {translate key="FacebookLoginErrorMessage"}

+ {/if} +
+
+ +
+ +
+
{setfocus key='EMAIL'} @@ -142,24 +133,24 @@ {include file="javascript-includes.tpl"} {include file='globalfooter.tpl'} \ No newline at end of file From 909631bc4a322c9ab50d5f2243e25e2a5a91a47c Mon Sep 17 00:00:00 2001 From: lucs7 Date: Sat, 8 Feb 2025 13:53:40 +0100 Subject: [PATCH 3/3] added LoginPresenterTest --- tests/Presenters/LoginPresenterTest.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/Presenters/LoginPresenterTest.php b/tests/Presenters/LoginPresenterTest.php index 57545020e..6596a0b28 100644 --- a/tests/Presenters/LoginPresenterTest.php +++ b/tests/Presenters/LoginPresenterTest.php @@ -223,11 +223,13 @@ class FakeLoginPage extends FakePageBase implements ILoginPage public $_ShowScheduleLink = false; public $_Announcements; - public function SetGoogleUrl($URL) { } + public function SetGoogleUrl($URL) {} - public function SetMicrosoftUrl($URL) { } + public function SetMicrosoftUrl($URL) {} - public function SetFacebookUrl($URL) { } + public function SetFacebookUrl($URL) {} + + public function SetKeycloakUrl($URL) {} public function PageLoad() {