IDEMPIERE-5346 SSO Support (#2038)
* IDEMPIERE-5346 SSO Support - add OIDC support to core
This commit is contained in:
parent
e61eab3901
commit
3ffdd2be0c
|
@ -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')
|
||||
;
|
||||
|
|
@ -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')
|
||||
;
|
||||
|
|
@ -687,4 +687,10 @@
|
|||
fragment="true"
|
||||
unpack="false"/>
|
||||
|
||||
<plugin
|
||||
id="org.idempiere.ui.sso.oidc"
|
||||
download-size="0"
|
||||
install-size="0"
|
||||
version="0.0.0"
|
||||
unpack="false"/>
|
||||
</feature>
|
||||
|
|
|
@ -440,6 +440,7 @@
|
|||
<setEntry value="org.idempiere.jetty.osgi.boot.fragment@default:false"/>
|
||||
<setEntry value="org.idempiere.keikai@default:false"/>
|
||||
<setEntry value="org.idempiere.printformat.editor@default:default"/>
|
||||
<setEntry value="org.idempiere.ui.sso.oidc@default:default"/>
|
||||
<setEntry value="org.idempiere.webservices@default:default"/>
|
||||
<setEntry value="org.idempiere.zk.billboard.chart@default:default"/>
|
||||
<setEntry value="org.idempiere.zk.billboard@default:false"/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="lib" path="lib/content-type.jar"/>
|
||||
<classpathentry exported="true" kind="lib" path="lib/accessors-smart.jar"/>
|
||||
<classpathentry exported="true" kind="lib" path="lib/lang-tag.jar"/>
|
||||
<classpathentry exported="true" kind="lib" path="lib/json-smart.jar"/>
|
||||
<classpathentry exported="true" kind="lib" path="lib/oauth2-oidc-sdk.jar"/>
|
||||
<classpathentry exported="true" kind="lib" path="lib/nimbus-jose-jwt.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.idempiere.ui.sso.oidc</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.ManifestBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.SchemaBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.ds.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.pde.PluginNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
|
@ -0,0 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
|
@ -0,0 +1,3 @@
|
|||
eclipse.preferences.version=1
|
||||
pluginProject.extensions=false
|
||||
resolve.requirebundle=false
|
|
@ -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,
|
||||
.
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.idempiere.ui.sso.oidc.factory.OIDCServiceFactory">
|
||||
<property name="service.ranking" type="Integer" value="0"/>
|
||||
<service>
|
||||
<provide interface="org.adempiere.base.sso.ISSOPrincipalFactory"/>
|
||||
</service>
|
||||
<implementation class="org.idempiere.ui.sso.oidc.factory.OIDCServiceFactory"/>
|
||||
</scr:component>
|
|
@ -0,0 +1,3 @@
|
|||
# org.idempiere.ui.sso.oidc
|
||||
|
||||
OpenID Connect SSO provider for iDempiere Web Client
|
|
@ -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
|
|
@ -0,0 +1,90 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.idempiere</groupId>
|
||||
<artifactId>org.idempiere.parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../org.idempiere.parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>org.idempiere.ui.sso.oidc</artifactId>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>auto-clean</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<filesets>
|
||||
<fileset>
|
||||
<directory>${project.basedir}/lib</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>oauth2-oidc-sdk</artifactId>
|
||||
<version>10.7.1</version>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>nimbus-jose-jwt</artifactId>
|
||||
<version>9.31</version>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>net.minidev</groupId>
|
||||
<artifactId>json-smart</artifactId>
|
||||
<version>2.4.10</version>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>lang-tag</artifactId>
|
||||
<version>1.7</version>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>net.minidev</groupId>
|
||||
<artifactId>accessors-smart</artifactId>
|
||||
<version>2.4.9</version>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>content-type</artifactId>
|
||||
<version>2.2</version>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<stripVersion>true</stripVersion>
|
||||
<overWriteReleases>true</overWriteReleases>
|
||||
<overWriteSnapshots>true</overWriteSnapshots>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
1
pom.xml
1
pom.xml
|
@ -56,6 +56,7 @@
|
|||
<module>org.idempiere.zk-feature</module>
|
||||
<module>org.idempiere.webservices.client-feature</module>
|
||||
<module>org.idempiere.jetty.osgi.boot.fragment</module>
|
||||
<module>org.idempiere.ui.sso.oidc</module>
|
||||
<module>org.idempiere.p2</module>
|
||||
<module>org.idempiere.javadoc</module>
|
||||
<module>org.idempiere.test-feature</module>
|
||||
|
|
Loading…
Reference in New Issue