diff --git a/migration/iD11/oracle/202310021327_IDEMPIERE-5346.sql b/migration/iD11/oracle/202310021327_IDEMPIERE-5346.sql
new file mode 100644
index 0000000000..4715fd9ed5
--- /dev/null
+++ b/migration/iD11/oracle/202310021327_IDEMPIERE-5346.sql
@@ -0,0 +1,10 @@
+-- IDEMPIERE-5346 SSO Support
+SELECT register_migration_script('202310021327_IDEMPIERE-5346.sql') FROM dual;
+
+SET SQLBLANKLINES ON
+SET DEFINE OFF
+
+-- Oct 2, 2023, 1:27:01 PM MYT
+INSERT INTO AD_Ref_List (AD_Ref_List_ID,Name,AD_Reference_ID,Value,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,AD_Ref_List_UU) VALUES (200656,'OpenID Connect',200213,'OIDC',0,0,'Y',TO_TIMESTAMP('2023-10-02 13:27:00','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-10-02 13:27:00','YYYY-MM-DD HH24:MI:SS'),100,'D','e1913c90-dcac-4a0d-bd55-dfa3408654e8')
+;
+
diff --git a/migration/iD11/postgresql/202310021327_IDEMPIERE-5346.sql b/migration/iD11/postgresql/202310021327_IDEMPIERE-5346.sql
new file mode 100644
index 0000000000..16ea241ce3
--- /dev/null
+++ b/migration/iD11/postgresql/202310021327_IDEMPIERE-5346.sql
@@ -0,0 +1,7 @@
+-- IDEMPIERE-5346 SSO Support
+SELECT register_migration_script('202310021327_IDEMPIERE-5346.sql') FROM dual;
+
+-- Oct 2, 2023, 1:27:01 PM MYT
+INSERT INTO AD_Ref_List (AD_Ref_List_ID,Name,AD_Reference_ID,Value,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,AD_Ref_List_UU) VALUES (200656,'OpenID Connect',200213,'OIDC',0,0,'Y',TO_TIMESTAMP('2023-10-02 13:27:00','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-10-02 13:27:00','YYYY-MM-DD HH24:MI:SS'),100,'D','e1913c90-dcac-4a0d-bd55-dfa3408654e8')
+;
+
diff --git a/org.adempiere.server-feature/feature.xml b/org.adempiere.server-feature/feature.xml
index ffe74c08c4..c7a369b6cc 100644
--- a/org.adempiere.server-feature/feature.xml
+++ b/org.adempiere.server-feature/feature.xml
@@ -687,4 +687,10 @@
fragment="true"
unpack="false"/>
+
diff --git a/org.adempiere.server-feature/server.product.launch b/org.adempiere.server-feature/server.product.launch
index c7a5073e90..e64e4fa5fa 100644
--- a/org.adempiere.server-feature/server.product.launch
+++ b/org.adempiere.server-feature/server.product.launch
@@ -440,6 +440,7 @@
+
diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WResetPassword.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WResetPassword.java
index 04befae6d3..fccc26aabf 100644
--- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WResetPassword.java
+++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WResetPassword.java
@@ -42,7 +42,6 @@ import org.compiere.model.MPasswordHistory;
import org.compiere.model.MPasswordRule;
import org.compiere.model.MSysConfig;
import org.compiere.model.MUser;
-import org.compiere.model.PO;
import org.compiere.model.SystemIDs;
import org.compiere.util.CLogger;
import org.compiere.util.DisplayType;
diff --git a/org.idempiere.ui.sso.oidc/.classpath b/org.idempiere.ui.sso.oidc/.classpath
new file mode 100644
index 0000000000..339a695181
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/.classpath
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.idempiere.ui.sso.oidc/.project b/org.idempiere.ui.sso.oidc/.project
new file mode 100644
index 0000000000..8b4153ca1b
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/.project
@@ -0,0 +1,33 @@
+
+
+ org.idempiere.ui.sso.oidc
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.pde.ds.core.builder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/org.idempiere.ui.sso.oidc/.settings/org.eclipse.core.resources.prefs b/org.idempiere.ui.sso.oidc/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000000..99f26c0203
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/org.idempiere.ui.sso.oidc/.settings/org.eclipse.pde.core.prefs b/org.idempiere.ui.sso.oidc/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 0000000000..f29e940a00
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/org.idempiere.ui.sso.oidc/META-INF/MANIFEST.MF b/org.idempiere.ui.sso.oidc/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..557f965ffa
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/META-INF/MANIFEST.MF
@@ -0,0 +1,27 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: OIDC Provider
+Bundle-SymbolicName: org.idempiere.ui.sso.oidc
+Bundle-Version: 11.0.0.qualifier
+Bundle-Vendor: iDempiere.org
+Bundle-RequiredExecutionEnvironment: JavaSE-17
+Automatic-Module-Name: org.idempiere.ui.sso.oidc
+Import-Package: javax.servlet;version="4.0.0",
+ javax.servlet.http;version="4.0.0",
+ org.osgi.framework;version="1.3.0",
+ org.osgi.service.component.annotations;version="1.3.0",
+ org.slf4j;version="1.7.30",
+ org.slf4j.event;version="1.7.30",
+ org.slf4j.helpers;version="1.7.30",
+ org.slf4j.spi;version="1.7.30"
+Bundle-ActivationPolicy: lazy
+Require-Bundle: org.adempiere.base;bundle-version="11.0.0",
+ org.eclipse.core.runtime;bundle-version="3.24.100"
+Service-Component: OSGI-INF/org.idempiere.ui.sso.oidc.factory.OIDCServiceFactory.xml
+Bundle-ClassPath: lib/nimbus-jose-jwt.jar,
+ lib/oauth2-oidc-sdk.jar,
+ lib/json-smart.jar,
+ lib/lang-tag.jar,
+ lib/accessors-smart.jar,
+ lib/content-type.jar,
+ .
diff --git a/org.idempiere.ui.sso.oidc/OSGI-INF/org.idempiere.ui.sso.oidc.factory.OIDCServiceFactory.xml b/org.idempiere.ui.sso.oidc/OSGI-INF/org.idempiere.ui.sso.oidc.factory.OIDCServiceFactory.xml
new file mode 100644
index 0000000000..302fcd325b
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/OSGI-INF/org.idempiere.ui.sso.oidc.factory.OIDCServiceFactory.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/org.idempiere.ui.sso.oidc/README.md b/org.idempiere.ui.sso.oidc/README.md
new file mode 100644
index 0000000000..7996860813
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/README.md
@@ -0,0 +1,3 @@
+# org.idempiere.ui.sso.oidc
+
+OpenID Connect SSO provider for iDempiere Web Client
diff --git a/org.idempiere.ui.sso.oidc/build.properties b/org.idempiere.ui.sso.oidc/build.properties
new file mode 100644
index 0000000000..81f7c3e769
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/build.properties
@@ -0,0 +1,11 @@
+source.. = src/
+output.. = target/classes/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ lib/nimbus-jose-jwt.jar,\
+ lib/oauth2-oidc-sdk.jar,\
+ lib/json-smart.jar,\
+ lib/lang-tag.jar,\
+ lib/accessors-smart.jar,\
+ lib/content-type.jar
diff --git a/org.idempiere.ui.sso.oidc/pom.xml b/org.idempiere.ui.sso.oidc/pom.xml
new file mode 100644
index 0000000000..16c669cb48
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/pom.xml
@@ -0,0 +1,90 @@
+
+ 4.0.0
+
+ org.idempiere
+ org.idempiere.parent
+ ${revision}
+ ../org.idempiere.parent/pom.xml
+
+ org.idempiere.ui.sso.oidc
+ eclipse-plugin
+
+
+
+
+ maven-clean-plugin
+
+
+ auto-clean
+ validate
+
+ clean
+
+
+
+
+
+
+ ${project.basedir}/lib
+
+ *.jar
+
+ false
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ validate
+
+ copy
+
+
+
+
+ com.nimbusds
+ oauth2-oidc-sdk
+ 10.7.1
+
+
+ com.nimbusds
+ nimbus-jose-jwt
+ 9.31
+
+
+ net.minidev
+ json-smart
+ 2.4.10
+
+
+ com.nimbusds
+ lang-tag
+ 1.7
+
+
+ net.minidev
+ accessors-smart
+ 2.4.9
+
+
+ com.nimbusds
+ content-type
+ 2.2
+
+
+ lib
+ true
+ true
+ true
+
+
+
+
+
+
+
diff --git a/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/factory/OIDCServiceFactory.java b/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/factory/OIDCServiceFactory.java
new file mode 100644
index 0000000000..b96efd378a
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/factory/OIDCServiceFactory.java
@@ -0,0 +1,58 @@
+/***********************************************************************
+ * This file is part of iDempiere ERP Open Source *
+ * http://www.idempiere.org *
+ * *
+ * Copyright (C) Contributors *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software *
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
+ * MA 02110-1301, USA. *
+ * *
+ * Contributors: *
+ * - hengsin *
+ **********************************************************************/
+package org.idempiere.ui.sso.oidc.factory;
+
+import org.adempiere.base.sso.ISSOPrincipalFactory;
+import org.adempiere.base.sso.ISSOPrincipalService;
+import org.compiere.model.I_SSO_PrincipalConfig;
+import org.osgi.service.component.annotations.Component;
+
+import org.idempiere.ui.sso.oidc.service.OIDCPrincipalService;
+
+/**
+ * Factory for OIDC principal service
+ * @author hengsin
+ */
+@Component(immediate = true, service = org.adempiere.base.sso.ISSOPrincipalFactory.class, property = {"service.ranking:Integer=0"})
+public class OIDCServiceFactory implements ISSOPrincipalFactory {
+
+ /** SSO provider id for OIDC */
+ public static final String OIDC_PROVIDER_ID = "OIDC";
+
+ /**
+ * Default constructor
+ */
+ public OIDCServiceFactory() {
+ }
+
+ @Override
+ public ISSOPrincipalService getSSOPrincipalService(I_SSO_PrincipalConfig config) {
+ if (config.getSSO_Provider().equals(OIDC_PROVIDER_ID))
+ return new OIDCPrincipalService(config);
+
+ return null;
+ }
+
+}
diff --git a/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/service/OIDCPrincipalService.java b/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/service/OIDCPrincipalService.java
new file mode 100644
index 0000000000..51f29df7b9
--- /dev/null
+++ b/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/service/OIDCPrincipalService.java
@@ -0,0 +1,305 @@
+/***********************************************************************
+ * This file is part of iDempiere ERP Open Source *
+ * http://www.idempiere.org *
+ * *
+ * Copyright (C) Contributors *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software *
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
+ * MA 02110-1301, USA. *
+ * *
+ * Contributors: *
+ * - hengsin *
+ **********************************************************************/
+package org.idempiere.ui.sso.oidc.service;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.adempiere.base.sso.ISSOPrincipalService;
+import org.adempiere.base.sso.SSOUtils;
+import org.compiere.model.I_SSO_PrincipalConfig;
+import org.compiere.model.MSysConfig;
+import org.compiere.util.Language;
+import org.compiere.util.Util;
+
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.GeneralException;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.Nonce;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
+import com.nimbusds.openid.connect.sdk.Prompt;
+import com.nimbusds.openid.connect.sdk.UserInfoRequest;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+/**
+ * Implement SSO principal provider service for OIDC
+ * @author hengsin
+ */
+public class OIDCPrincipalService implements ISSOPrincipalService {
+
+ /** code parameter from OIDC */
+ private static final String AUTHENTICATION_CODE_PARAMETER = "code";
+
+ private static final String OIDC_STATE = "oidc.state";
+
+ private I_SSO_PrincipalConfig principalConfig;
+
+ private OIDCProviderMetadata metaData = null;
+
+ /**
+ * Default constructor
+ * @param principleConfig
+ */
+ public OIDCPrincipalService(I_SSO_PrincipalConfig principleConfig) {
+ this.principalConfig = principleConfig;
+ }
+
+ @Override
+ public String getUserName(Object principalObject) throws ParseException {
+ if (principalObject != null && principalObject instanceof UserInfo)
+ {
+ boolean isEmailLogin = MSysConfig.getBooleanValue(MSysConfig.USE_EMAIL_FOR_LOGIN, false);
+ UserInfo userInfo = (UserInfo) principalObject;
+ if (isEmailLogin)
+ return (String) userInfo.getEmailAddress();
+ else
+ return (String) userInfo.getName();
+ }
+ return null;
+ }
+
+ @Override
+ public Language getLanguage(Object principalObject) throws ParseException {
+ return Language.getBaseLanguage();
+ }
+
+ @Override
+ public boolean hasAuthenticationCode(HttpServletRequest request, HttpServletResponse response) {
+ String code = request.getParameter(AUTHENTICATION_CODE_PARAMETER);
+ return !Util.isEmpty(code, true);
+ }
+
+ @Override
+ public void getAuthenticationToken(HttpServletRequest request, HttpServletResponse response, String redirectMode)
+ throws Throwable {
+ String url = request.getRequestURL().toString();
+ String query = request.getQueryString();
+ if (query != null) {
+ url += "?" + query;
+ }
+ AuthenticationResponse authResponse = AuthenticationResponseParser.parse(new URI(url));
+
+ // Check the returned state parameter, must match the original
+ State state = (State) request.getSession().getAttribute(OIDC_STATE);
+ if (!state.equals(authResponse.getState())) {
+ // Unexpected or tampered response, stop!!!
+ request.getSession().removeAttribute(OIDC_STATE);
+ response.sendRedirect(getRedirectURL(principalConfig, redirectMode));
+ return;
+ }
+
+ if (!authResponse.indicatesSuccess()) {
+ // The request was denied or some error occurred
+ AuthenticationErrorResponse errorResponse = authResponse.toErrorResponse();
+ throw new RuntimeException(errorResponse.toString());
+ }
+
+ AuthenticationSuccessResponse successResponse = authResponse.toSuccessResponse();
+
+ // Retrieve the authorization code, to be used later to exchange the code for
+ // an access token at the token endpoint of the server
+ AuthorizationCode authorizationCode = successResponse.getAuthorizationCode();
+ URI redirectURI = new URI(getRedirectURL(principalConfig, redirectMode));
+ AuthorizationGrant authorizationGrant = new AuthorizationCodeGrant(authorizationCode, redirectURI);
+
+ TokenResponse tokenResponse = issueTokenRequest(authorizationGrant);
+
+ processTokenResponse(request, tokenResponse, true);
+ }
+
+ /**
+ * @param principalConfig
+ * @param clientType SSO_MODE_WEBUI, SSO_MODE_MONITOR or SSO_MODE_OSGI (webui, server monitor, osgi console)
+ * @return redirect url for clientType
+ */
+ private String getRedirectURL(I_SSO_PrincipalConfig principalConfig, String clientType) {
+ if (SSOUtils.SSO_MODE_WEBUI.equals(clientType))
+ return principalConfig.getSSO_ApplicationRedirectURIs();
+ else if (SSOUtils.SSO_MODE_MONITOR.equals(clientType))
+ return principalConfig.getSSO_IDempMonitorRedirectURIs();
+ else if (SSOUtils.SSO_MODE_OSGI.equals(clientType))
+ return principalConfig.getSSO_OSGIRedirectURIs();
+ else
+ return null;
+ }
+
+ /**
+ * Process token response from keycloak server
+ * @param request
+ * @param tokenResponse
+ * @param getUserInfo true to get UserInfo from userinfo endpoint
+ * @throws URISyntaxException
+ * @throws IOException
+ * @throws GeneralException
+ */
+ private void processTokenResponse(HttpServletRequest request, TokenResponse tokenResponse, boolean getUserInfo)
+ throws URISyntaxException, IOException, GeneralException {
+ if (!tokenResponse.indicatesSuccess()) {
+ // We got an error response...
+ TokenErrorResponse errorResponse = tokenResponse.toErrorResponse();
+ throw new RuntimeException(errorResponse.toJSONObject().toJSONString());
+ }
+
+ OIDCTokenResponse oidcTokenResponse = (OIDCTokenResponse) tokenResponse.toSuccessResponse();
+
+ // Get the access token
+ AccessToken accessToken = oidcTokenResponse.getOIDCTokens().getAccessToken();
+
+ if (getUserInfo) {
+ String userInfoURL = getMetaData().getUserInfoEndpointURI().toString();
+ URI userInfoEndpoint = new URI(userInfoURL);
+ BearerAccessToken bearerAccessToken = new BearerAccessToken(accessToken.getValue()); // The access token
+
+ // Make the request
+ HTTPResponse httpResponse = new UserInfoRequest(userInfoEndpoint, bearerAccessToken)
+ .toHTTPRequest()
+ .send();
+
+ // Parse the response
+ UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse);
+
+ if (!userInfoResponse.indicatesSuccess()) {
+ // The request failed, e.g. due to invalid or expired token
+ throw new RuntimeException(userInfoResponse.toErrorResponse().toString());
+ }
+
+ // Extract the claims
+ UserInfo userInfo = userInfoResponse.toSuccessResponse().getUserInfo();
+ setAuthenticationResult(request.getSession(), userInfo);
+ }
+ }
+
+ private OIDCProviderMetadata getMetaData() throws GeneralException, IOException {
+ if (metaData == null) {
+ String discoveryURI = principalConfig.getSSO_ApplicationDiscoveryURI();
+ Issuer issuer = new Issuer(discoveryURI.substring(0, discoveryURI.indexOf("/.well-known/openid-configuration")));
+ metaData = OIDCProviderMetadata.resolve(issuer);
+ }
+ return metaData;
+ }
+
+ /**
+ * Send Token request to oidc token endpoint
+ * @param authorizationGrant
+ * @return TokenResponse
+ * @throws URISyntaxException
+ * @throws IOException
+ * @throws GeneralException
+ */
+ private TokenResponse issueTokenRequest(AuthorizationGrant authorizationGrant) throws URISyntaxException,
+ IOException, GeneralException {
+ // The credentials to authenticate the client at the token endpoint
+ ClientID clientID = new ClientID(principalConfig.getSSO_ApplicationClientID());
+ Secret clientSecret = new Secret(principalConfig.getSSO_ApplicationSecretKey());
+ ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret);
+
+ // The token endpoint
+ String tokenEndpointURL = getMetaData().getTokenEndpointURI().toString();
+ URI tokenEndpoint = new URI(tokenEndpointURL);
+
+ // Make the token request
+ TokenRequest tokenRequest = new TokenRequest(tokenEndpoint, clientAuth, authorizationGrant);
+
+ return OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send());
+ }
+
+ /**
+ * Store UserInfo as session token
+ * @param session
+ * @param userInfo
+ */
+ private void setAuthenticationResult(HttpSession session, UserInfo userInfo) {
+ session.setAttribute(ISSOPrincipalService.SSO_PRINCIPAL_SESSION_TOKEN, userInfo);
+ }
+
+ @Override
+ public boolean isAuthenticated(HttpServletRequest request, HttpServletResponse response) {
+ return request.getSession().getAttribute(ISSOPrincipalService.SSO_PRINCIPAL_SESSION_TOKEN) != null;
+ }
+
+ @Override
+ public void redirectForAuthentication(HttpServletRequest request, HttpServletResponse response, String redirectMode)
+ throws IOException {
+ AuthenticationRequest authRequest = null;
+ try {
+ String url = getMetaData().getAuthorizationEndpointURI().toString();
+ authRequest = new AuthenticationRequest.Builder(
+ new ResponseType(AUTHENTICATION_CODE_PARAMETER),
+ new Scope("openid", "profile", "email"),
+ new ClientID(principalConfig.getSSO_ApplicationClientID()),
+ new URI(getRedirectURL(principalConfig, redirectMode)))
+ .state(new State())
+ .nonce(new Nonce())
+ .prompt(new Prompt(Prompt.Type.LOGIN))
+ .endpointURI(new URI(url))
+ .build();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ } catch (GeneralException e) {
+ throw new RuntimeException(e);
+ }
+
+ //store state for response verification
+ request.getSession().setAttribute(OIDC_STATE, authRequest.getState());
+ URI redirectURI = authRequest.toURI();
+
+ response.sendRedirect(redirectURI.toURL().toString());
+ }
+
+ @Override
+ public void removePrincipalFromSession(HttpServletRequest httpRequest) {
+ httpRequest.getSession().removeAttribute(ISSOPrincipalService.SSO_PRINCIPAL_SESSION_TOKEN);
+ httpRequest.getSession().removeAttribute(OIDC_STATE);
+ }
+}
diff --git a/pom.xml b/pom.xml
index f955a32e3d..9dcbcf11dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,7 @@
org.idempiere.zk-feature
org.idempiere.webservices.client-feature
org.idempiere.jetty.osgi.boot.fragment
+ org.idempiere.ui.sso.oidc
org.idempiere.p2
org.idempiere.javadoc
org.idempiere.test-feature