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"
|
fragment="true"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
|
<plugin
|
||||||
|
id="org.idempiere.ui.sso.oidc"
|
||||||
|
download-size="0"
|
||||||
|
install-size="0"
|
||||||
|
version="0.0.0"
|
||||||
|
unpack="false"/>
|
||||||
</feature>
|
</feature>
|
||||||
|
|
|
@ -440,6 +440,7 @@
|
||||||
<setEntry value="org.idempiere.jetty.osgi.boot.fragment@default:false"/>
|
<setEntry value="org.idempiere.jetty.osgi.boot.fragment@default:false"/>
|
||||||
<setEntry value="org.idempiere.keikai@default:false"/>
|
<setEntry value="org.idempiere.keikai@default:false"/>
|
||||||
<setEntry value="org.idempiere.printformat.editor@default:default"/>
|
<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.webservices@default:default"/>
|
||||||
<setEntry value="org.idempiere.zk.billboard.chart@default:default"/>
|
<setEntry value="org.idempiere.zk.billboard.chart@default:default"/>
|
||||||
<setEntry value="org.idempiere.zk.billboard@default:false"/>
|
<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.MPasswordRule;
|
||||||
import org.compiere.model.MSysConfig;
|
import org.compiere.model.MSysConfig;
|
||||||
import org.compiere.model.MUser;
|
import org.compiere.model.MUser;
|
||||||
import org.compiere.model.PO;
|
|
||||||
import org.compiere.model.SystemIDs;
|
import org.compiere.model.SystemIDs;
|
||||||
import org.compiere.util.CLogger;
|
import org.compiere.util.CLogger;
|
||||||
import org.compiere.util.DisplayType;
|
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.zk-feature</module>
|
||||||
<module>org.idempiere.webservices.client-feature</module>
|
<module>org.idempiere.webservices.client-feature</module>
|
||||||
<module>org.idempiere.jetty.osgi.boot.fragment</module>
|
<module>org.idempiere.jetty.osgi.boot.fragment</module>
|
||||||
|
<module>org.idempiere.ui.sso.oidc</module>
|
||||||
<module>org.idempiere.p2</module>
|
<module>org.idempiere.p2</module>
|
||||||
<module>org.idempiere.javadoc</module>
|
<module>org.idempiere.javadoc</module>
|
||||||
<module>org.idempiere.test-feature</module>
|
<module>org.idempiere.test-feature</module>
|
||||||
|
|
Loading…
Reference in New Issue