+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.database;
+
+import org.apache.knox.gateway.services.token.impl.TokenStateDatabase;
+
+import javax.sql.DataSource;
+
+public class KnoxDatabase {
+
+ protected final DataSource dataSource;
+
+ public KnoxDatabase(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ protected void createTableIfNotExists(String tableName, String createSqlFileName) throws Exception {
+ if (!JDBCUtils.tableExists(tableName, dataSource)) {
+ JDBCUtils.createTableFromSQL(createSqlFileName, dataSource, TokenStateDatabase.class.getClassLoader());
+ }
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/deploy/DeploymentFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/deploy/DeploymentFactory.java
index dfe4a4ea90..564f793007 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/deploy/DeploymentFactory.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/deploy/DeploymentFactory.java
@@ -376,6 +376,13 @@ private static void initialize(
GatewayConfig gatewayConfig) {
WebAppDescriptor wad = context.getWebAppDescriptor();
String topoName = context.getTopology().getName();
+
+ final boolean hasKnoxIdf = services!= null && services.entrySet().stream().anyMatch( e -> e.getKey().equalsIgnoreCase("KNOXIDF") );
+ if (hasKnoxIdf) {
+ wad.createServlet().servletName("auth-consent-redirect").servletClass("org.apache.knox.gateway.service.knoxidf.AuthConsentServlet");
+ wad.createServletMapping().servletName("auth-consent-redirect").urlPattern("/authConsent");
+ }
+
boolean asyncSupported = gatewayConfig.isAsyncSupported() || gatewayConfig.isTopologyAsyncSupported(topoName);
if( applications == null ) {
String servletName = topoName + SERVLET_NAME_SUFFIX;
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
index 06ce95d93a..cdbafc7bbb 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
@@ -90,6 +90,8 @@ public void init(GatewayConfig config, Map options) throws Servic
ldapService.init(config, options);
addService(ServiceType.LDAP_SERVICE, ldapService);
}
+
+ addService(ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE, gatewayServiceFactory.create(this, ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE, config, options));
}
@Override
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/factory/FederatedIdentityServiceFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/factory/FederatedIdentityServiceFactory.java
new file mode 100644
index 0000000000..f78f96af6d
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/factory/FederatedIdentityServiceFactory.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.services.factory;
+
+import org.apache.knox.gateway.GatewayMessages;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.Service;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.knoxidf.federation.EmptyFederatedIdentitityService;
+import org.apache.knox.gateway.services.knoxidf.federation.FederatedIdentityService;
+import org.apache.knox.gateway.services.knoxidf.federation.JdbcFederatedIdentityService;
+import org.apache.knox.gateway.services.topology.TopologyService;
+import org.apache.knox.gateway.topology.Topology;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class FederatedIdentityServiceFactory extends AbstractServiceFactory {
+
+ private static final GatewayMessages LOG = MessagesFactory.get(GatewayMessages.class);
+ private static final String DEFAULT_IMPLEMENTATION = EmptyFederatedIdentitityService.class.getName();
+
+ @Override
+ protected Service createService(GatewayServices gatewayServices, ServiceType serviceType, GatewayConfig gatewayConfig, Map options, String implementation)
+ throws ServiceLifecycleException {
+
+ String implementationToUse = implementation;
+ // If implementation is empty, check if we should auto-enable JdbcFederatedIdentityService
+ if (isEmptyDefaultImplementation(implementationToUse)) {
+ if (isKnoxIdfEnabledInAnyTopology(gatewayServices)) {
+ implementationToUse = JdbcFederatedIdentityService.class.getName();
+ }
+ }
+
+ FederatedIdentityService service = null;
+ if (shouldCreateService(implementationToUse)) {
+ if (matchesImplementation(implementationToUse, EmptyFederatedIdentitityService.class, true)) {
+ service = new EmptyFederatedIdentitityService();
+ } else if (matchesImplementation(implementationToUse, JdbcFederatedIdentityService.class)) {
+ try {
+ try {
+ service = new JdbcFederatedIdentityService();
+ ((JdbcFederatedIdentityService) service).setAliasService(getAliasService(gatewayServices));
+ service.init(gatewayConfig, options);
+ } catch (ServiceLifecycleException e) {
+ LOG.errorInitializingService(implementationToUse, e.getMessage(), e);
+ service = new EmptyFederatedIdentitityService();
+ }
+ } catch (Exception e) {
+ throw new ServiceLifecycleException("Error while creating Federated Identity Service: " + e, e);
+ }
+ }
+ logServiceUsage(service.getClass().getName(), serviceType);
+ }
+ return service;
+ }
+
+ private boolean isKnoxIdfEnabledInAnyTopology(GatewayServices gatewayServices) {
+ final TopologyService topologyService = gatewayServices.getService(ServiceType.TOPOLOGY_SERVICE);
+ if (topologyService != null) {
+ for (Topology topology : topologyService.getTopologies()) {
+ if (topology.getServices().stream().anyMatch(service -> "KNOXIDF".equals(service.getRole()))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected ServiceType getServiceType() {
+ return ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE;
+ }
+
+ @Override
+ protected Collection getKnownImplementations() {
+ return List.of(DEFAULT_IMPLEMENTATION, JdbcFederatedIdentityService.class.getName());
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/EmptyFederatedIdentitityService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/EmptyFederatedIdentitityService.java
new file mode 100644
index 0000000000..8ce9e550e9
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/EmptyFederatedIdentitityService.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.services.knoxidf.federation;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+
+import java.util.Map;
+import java.util.Optional;
+
+public class EmptyFederatedIdentitityService implements FederatedIdentityService {
+ @Override
+ public void addFederatedIdentity(FederatedIdentity identity) {
+ }
+
+ @Override
+ public Optional findById(String identityId) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findByProviderAndSubject(String provider, String externalIssuer, String externalSubject) {
+ return Optional.empty();
+ }
+
+ @Override
+ public void init(GatewayConfig config, Map options) throws ServiceLifecycleException {
+ }
+
+ @Override
+ public void start() throws ServiceLifecycleException {
+ }
+
+ @Override
+ public void stop() throws ServiceLifecycleException {
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityDatabase.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityDatabase.java
new file mode 100644
index 0000000000..649bf30388
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityDatabase.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.services.knoxidf.federation;
+
+import org.apache.knox.gateway.i18n.messages.Message;
+import org.apache.knox.gateway.i18n.messages.MessageLevel;
+import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
+
+@Messages(logger="org.apache.knox.gateway.knoxidf.federated.identity.service")
+public interface FederatedIdentityServiceMessages {
+
+ @Message(level = MessageLevel.ERROR, text = "An error occurred while saving federated identity {0} in the database : {1}")
+ void errorSavingFederatedIdentityInDatabase(String federatedIdentityId, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+ @Message(level = MessageLevel.ERROR, text = "An error occurred while fetching federated identity ({0} / {1} / {2}) from the database : {3}")
+ void errorFetchingFederatedIdentityFromDatabase(String provider, String issuer, String subject, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+ @Message(level = MessageLevel.ERROR, text = "An error occurred while fetching federated identity ({0}) from the database : {1}")
+ void errorFetchingFederatedIdentityFromDatabase(String id, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/JdbcFederatedIdentityService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/JdbcFederatedIdentityService.java
new file mode 100644
index 0000000000..8e26bdc1d5
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/JdbcFederatedIdentityService.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.services.knoxidf.federation;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.database.DataSourceProvider;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AliasService;
+
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class JdbcFederatedIdentityService implements FederatedIdentityService {
+ private static final FederatedIdentityServiceMessages LOG = MessagesFactory.get(FederatedIdentityServiceMessages.class);
+
+ private final AtomicBoolean initialized = new AtomicBoolean(false);
+ private final Lock initLock = new ReentrantLock(true);
+ private AliasService aliasService; // connection username/pw are stored here
+ private FederatedIdentityDatabase federatedIdentityDatabase;
+
+ @Override
+ public void init(GatewayConfig config, Map options) throws ServiceLifecycleException {
+ if (!initialized.get()) {
+ initLock.lock();
+ try {
+ if (aliasService == null) {
+ throw new ServiceLifecycleException("The required AliasService reference has not been set.");
+ }
+ try {
+ this.federatedIdentityDatabase = new FederatedIdentityDatabase(DataSourceProvider.getDataSource(config, aliasService), config.getDatabaseType());
+ initialized.set(true);
+ } catch (Exception e) {
+ throw new ServiceLifecycleException("Error while initiating JDBCTokenStateService: " + e, e);
+ }
+ } finally {
+ initLock.unlock();
+ }
+ }
+ }
+
+ @Override
+ public void start() throws ServiceLifecycleException {
+ }
+
+ @Override
+ public void stop() throws ServiceLifecycleException {
+ }
+
+ public void setAliasService(AliasService aliasService) {
+ this.aliasService = aliasService;
+ }
+
+ protected AliasService getAliasService() {
+ return aliasService;
+ }
+
+ @Override
+ public void addFederatedIdentity(FederatedIdentity identity) {
+ try {
+ if (findByProviderAndSubject(identity.getProvider(), identity.getExternalIssuer(), identity.getExternalSubject()).isEmpty()) {
+ federatedIdentityDatabase.addFederatedIdentity(identity);
+ }
+ } catch (SQLException e) {
+ LOG.errorSavingFederatedIdentityInDatabase(identity.getId(), e.getMessage(), e);
+ throw new FederatedIdentityServiceException("An error occurred while saving Federated Identity " + identity.getId() + " in the database", e);
+ }
+ }
+
+ @Override
+ public Optional findByProviderAndSubject(String provider, String issuer, String subject) {
+ try {
+ return federatedIdentityDatabase.findByProviderAndSubject(provider, issuer, subject);
+ } catch (SQLException e) {
+ LOG.errorFetchingFederatedIdentityFromDatabase(provider, subject, issuer, e.getMessage(), e);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findById(String id) {
+ try {
+ return federatedIdentityDatabase.findById(id);
+ } catch (SQLException e) {
+ LOG.errorFetchingFederatedIdentityFromDatabase(id, e.getMessage(), e);
+ }
+ return Optional.empty();
+ }
+
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
index dbc89d6950..c7579a4805 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
@@ -19,7 +19,7 @@
import org.apache.commons.codec.binary.Base64;
import org.apache.knox.gateway.database.DatabaseType;
-import org.apache.knox.gateway.database.JDBCUtils;
+import org.apache.knox.gateway.database.KnoxDatabase;
import org.apache.knox.gateway.services.security.token.KnoxToken;
import org.apache.knox.gateway.services.security.token.TokenMetadata;
@@ -37,7 +37,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
-public class TokenStateDatabase {
+public class TokenStateDatabase extends KnoxDatabase {
static final String TOKENS_TABLE_NAME = "KNOX_TOKENS";
static final String TOKEN_METADATA_TABLE_NAME = "KNOX_TOKEN_METADATA";
private static final String ADD_TOKEN_SQL = "INSERT INTO " + TOKENS_TABLE_NAME + "(token_id, issue_time, expiration, max_lifetime) VALUES(?, ?, ?, ?)";
@@ -58,21 +58,13 @@ public class TokenStateDatabase {
private static final String GET_TOKENS_CREATED_BY_USER_NAME_SQL = GET_ALL_TOKENS_SQL + " AND kt.token_id IN (SELECT token_id FROM " + TOKEN_METADATA_TABLE_NAME + " WHERE md_name = '" + TokenMetadata.CREATED_BY + "' AND md_value = ? )"
+ " ORDER BY kt.issue_time";
- private final DataSource dataSource;
-
TokenStateDatabase(DataSource dataSource, String dbType) throws Exception {
- this.dataSource = dataSource;
+ super(dataSource);
DatabaseType databaseType = DatabaseType.fromString(dbType);
createTableIfNotExists(TOKENS_TABLE_NAME, databaseType.tokensTableSql());
createTableIfNotExists(TOKEN_METADATA_TABLE_NAME, databaseType.metadataTableSql());
}
- private void createTableIfNotExists(String tableName, String createSqlFileName) throws Exception {
- if (!JDBCUtils.tableExists(tableName, dataSource)) {
- JDBCUtils.createTableFromSQL(createSqlFileName, dataSource, TokenStateDatabase.class.getClassLoader());
- }
- }
-
boolean addToken(String tokenId, long issueTime, long expiration, long maxLifetimeDuration) throws SQLException {
try (Connection connection = dataSource.getConnection(); PreparedStatement addTokenStatement = connection.prepareStatement(ADD_TOKEN_SQL)) {
addTokenStatement.setString(1, tokenId);
diff --git a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ServiceFactory b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ServiceFactory
index 4c015979f9..c48af4a587 100644
--- a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ServiceFactory
+++ b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ServiceFactory
@@ -16,9 +16,14 @@
# limitations under the License.
##########################################################################
+# Please keep the alphabetical order of service factories!
+
org.apache.knox.gateway.services.factory.AliasServiceFactory
+org.apache.knox.gateway.services.factory.ConcurrentSessionVerifierFactory
org.apache.knox.gateway.services.factory.ClusterConfigurationMonitorServiceFactory
org.apache.knox.gateway.services.factory.CryptoServiceFactory
+org.apache.knox.gateway.services.factory.FederatedIdentityServiceFactory
+org.apache.knox.gateway.services.factory.GatewayStatusServiceFactory
org.apache.knox.gateway.services.factory.HostMappingServiceFactory
org.apache.knox.gateway.services.factory.KeystoreServiceFactory
org.apache.knox.gateway.services.factory.MasterServiceFactory
@@ -28,8 +33,6 @@ org.apache.knox.gateway.services.factory.ServerInfoServiceFactory
org.apache.knox.gateway.services.factory.ServiceDefinitionRegistryFactory
org.apache.knox.gateway.services.factory.ServiceRegistryServiceFactory
org.apache.knox.gateway.services.factory.SslServiceFactory
-org.apache.knox.gateway.services.factory.TokenServiceFactory
org.apache.knox.gateway.services.factory.TokenStateServiceFactory
org.apache.knox.gateway.services.factory.TopologyServiceFactory
-org.apache.knox.gateway.services.factory.ConcurrentSessionVerifierFactory
-org.apache.knox.gateway.services.factory.GatewayStatusServiceFactory
\ No newline at end of file
+org.apache.knox.gateway.services.factory.TokenServiceFactory
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTable.sql b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTable.sql
new file mode 100644
index 0000000000..239cb5df1a
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTable.sql
@@ -0,0 +1,21 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with this
+-- work for additional information regarding copyright ownership. The ASF
+-- licenses this file to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+-- License for the specific language governing permissions and limitations under
+-- the License.
+CREATE TABLE FEDERATED_IDENTITY_ATTR (
+ identity_id VARCHAR(36) NOT NULL,
+ attr_key VARCHAR(128) NOT NULL,
+ attr_value TEXT,
+ PRIMARY KEY (identity_id, attr_key),
+ FOREIGN KEY (identity_id) REFERENCES FEDERATED_IDENTITY (id) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTableDerby.sql b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTableDerby.sql
new file mode 100644
index 0000000000..13ae9ab113
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTableDerby.sql
@@ -0,0 +1,22 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with this
+-- work for additional information regarding copyright ownership. The ASF
+-- licenses this file to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+-- License for the specific language governing permissions and limitations under
+-- the License.
+
+CREATE TABLE FEDERATED_IDENTITY_ATTR (
+ identity_id VARCHAR(36),
+ attr_key VARCHAR(128),
+ attr_value CLOB,
+ PRIMARY KEY (identity_id, attr_key),
+ CONSTRAINT fk_fed_attr FOREIGN KEY (identity_id) REFERENCES FEDERATED_IDENTITY(id) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTableOracle.sql b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTableOracle.sql
new file mode 100644
index 0000000000..7ed07b1b05
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityAttributesTableOracle.sql
@@ -0,0 +1,22 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with this
+-- work for additional information regarding copyright ownership. The ASF
+-- licenses this file to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+-- License for the specific language governing permissions and limitations under
+-- the License.
+
+CREATE TABLE FEDERATED_IDENTITY_ATTR (
+ identity_id VARCHAR2(36) NOT NULL,
+ attr_key VARCHAR2(128) NOT NULL,
+ attr_value CLOB,
+ CONSTRAINT pk_fed_attr PRIMARY KEY (identity_id, attr_key),
+ CONSTRAINT fk_fed_attr FOREIGN KEY (identity_id) REFERENCES FEDERATED_IDENTITY(id) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTable.sql b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTable.sql
new file mode 100644
index 0000000000..acaf1c3404
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTable.sql
@@ -0,0 +1,25 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with this
+-- work for additional information regarding copyright ownership. The ASF
+-- licenses this file to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+-- License for the specific language governing permissions and limitations under
+-- the License.
+
+CREATE TABLE FEDERATED_IDENTITY (
+ id VARCHAR(36) PRIMARY KEY,
+ user_id VARCHAR(36) NOT NULL,
+ provider VARCHAR(64) NOT NULL,
+ external_subject VARCHAR(255) NOT NULL,
+ external_issuer VARCHAR(255) NOT NULL,
+ created_at TIMESTAMP NOT NULL
+);
+
+CREATE UNIQUE INDEX UX_FED_IDENTITY ON FEDERATED_IDENTITY (provider, external_issuer, external_subject);
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTableDerby.sql b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTableDerby.sql
new file mode 100644
index 0000000000..7152d4c71d
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTableDerby.sql
@@ -0,0 +1,25 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with this
+-- work for additional information regarding copyright ownership. The ASF
+-- licenses this file to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+-- License for the specific language governing permissions and limitations under
+-- the License.
+
+CREATE TABLE FEDERATED_IDENTITY (
+ id VARCHAR(36) PRIMARY KEY,
+ user_id VARCHAR(36),
+ provider VARCHAR(64),
+ external_subject VARCHAR(255),
+ external_issuer VARCHAR(255),
+ created_at TIMESTAMP
+);
+
+CREATE UNIQUE INDEX UX_FED_IDENTITY ON FEDERATED_IDENTITY (provider, external_issuer, external_subject);
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTableOracle.sql b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTableOracle.sql
new file mode 100644
index 0000000000..0dc42c1f72
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxIDFFederatedIdentityTableOracle.sql
@@ -0,0 +1,25 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with this
+-- work for additional information regarding copyright ownership. The ASF
+-- licenses this file to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+-- License for the specific language governing permissions and limitations under
+-- the License.
+
+CREATE TABLE FEDERATED_IDENTITY (
+ id VARCHAR2(36) PRIMARY KEY,
+ user_id VARCHAR2(36) NOT NULL,
+ provider VARCHAR2(64) NOT NULL,
+ external_subject VARCHAR2(255) NOT NULL,
+ external_issuer VARCHAR2(255) NOT NULL,
+ created_at TIMESTAMP NOT NULL
+);
+
+CREATE UNIQUE INDEX UX_FED_IDENTITY ON FEDERATED_IDENTITY (provider, external_issuer, external_subject);
\ No newline at end of file
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
index b27358c7cb..1a5483c537 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
@@ -66,7 +66,8 @@ public void testAddStartAndStop() throws ServiceLifecycleException {
ServiceType.CONCURRENT_SESSION_VERIFIER,
ServiceType.REMOTE_CONFIGURATION_MONITOR,
ServiceType.GATEWAY_STATUS_SERVICE,
- ServiceType.LDAP_SERVICE
+ ServiceType.LDAP_SERVICE,
+ ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE
};
assertNotEquals(ServiceType.values(), orderedServiceTypes);
diff --git a/gateway-service-knoxidf/pom.xml b/gateway-service-knoxidf/pom.xml
new file mode 100644
index 0000000000..48296b435d
--- /dev/null
+++ b/gateway-service-knoxidf/pom.xml
@@ -0,0 +1,115 @@
+
+
+
+ 4.0.0
+
+ org.apache.knox
+ gateway
+ 3.0.0-SNAPSHOT
+
+
+ gateway-service-knoxidf
+ gateway-service-knoxidf
+
+
+
+ org.apache.knox
+ gateway-i18n
+
+
+ org.apache.knox
+ gateway-spi
+
+
+ org.apache.knox
+ gateway-provider-jersey
+
+
+ org.apache.knox
+ gateway-util-common
+
+
+ org.apache.knox
+ gateway-service-knoxtoken
+
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+
+ com.google.guava
+ guava
+
+
+ commons-io
+ commons-io
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.uuid
+ java-uuid-generator
+
+
+ com.nimbusds
+ nimbus-jose-jwt
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.apache.commons
+ commons-text
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpcore
+
+
+ org.glassfish.jersey.core
+ jersey-common
+
+
+
diff --git a/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/AuthConsentServlet.java b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/AuthConsentServlet.java
new file mode 100644
index 0000000000..6200edc835
--- /dev/null
+++ b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/AuthConsentServlet.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.service.knoxidf;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import static org.apache.knox.gateway.util.knoxidf.KnoxIDFUtils.getRequestParamSafe;
+
+
+public class AuthConsentServlet extends HttpServlet {
+
+ @Context
+ UriInfo uriInfo;
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ response.setContentType("text/html;charset=UTF-8");
+ final String clientId = getRequestParamSafe(request, "client_id");
+ final String state = getRequestParamSafe(request, "state");
+ final String scope = getRequestParamSafe(request, "scope");
+ final Set scopes = new HashSet<>(Arrays.asList(scope.split("\\s+")));
+
+ try (PrintWriter out = response.getWriter()) {
+ out.println("");
+ out.println("Consent Required");
+ out.println("");
+ out.println("");
+ out.println("
");
+ out.println("
Application Consent Required
");
+ out.printf(Locale.US, "
The application %s is requesting access to your account.
%n", clientId);
+
+ if (!scopes.isEmpty()) {
+ out.println("
This application will be able to:
");
+ out.println("
");
+ for (String s : scopes) {
+ out.printf(Locale.US, "
%s
%n", describeScope(s));
+ }
+ out.println("
");
+ }
+
+ out.println("");
+ out.println("
");
+ out.println("");
+ out.println("");
+ }
+ }
+
+ private String describeScope(String scope) {
+ if (scope == null) {
+ return "";
+ }
+
+ switch (scope) {
+ case "openid":
+ return "Authenticate using your account";
+ case "profile":
+ return "View your basic profile information";
+ case "email":
+ return "View your email address";
+ case "address":
+ return "View your address information";
+ case "phone":
+ return "View your phone number";
+ case "calendar.read":
+ return "Read your calendar events";
+ case "calendar.write":
+ return "Modify your calendar events";
+ default:
+ return scope;
+ }
+ }
+
+ //Redirect target is application-local and state is encoded/controlled
+ @SuppressWarnings("UNVALIDATED_REDIRECT")
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ final String action = request.getParameter("action");
+ final String state = request.getParameter("state");
+ final String redirectUri = request.getServletContext().getContextPath() + "/" + AuthorizeResource.RESOURCE_PATH +
+ ("accept".equals(action) ? "/consentAccepted?state=" + state : "/consentDenied");
+ response.sendRedirect(redirectUri);
+ }
+
+}
diff --git a/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/AuthorizeResource.java b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/AuthorizeResource.java
new file mode 100644
index 0000000000..a49369ee4e
--- /dev/null
+++ b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/AuthorizeResource.java
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.service.knoxidf;
+
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.util.JsonUtils;
+import org.apache.knox.gateway.util.knoxidf.KnoxIDFConstants;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.ServletContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.knox.gateway.util.knoxidf.KnoxIDFConstants.BASE_RESORCE_PATH;
+
+@Path(BASE_RESORCE_PATH + "/.well-known/openid-configuration")
+@Produces(MediaType.APPLICATION_JSON)
+public class DiscoveryResource {
+ private String currentTopologyName;
+ private String tokenExchangeTopologyName;
+
+ @Context
+ private ServletContext servletContext;
+
+ @PostConstruct
+ public void init() {
+ tokenExchangeTopologyName = servletContext.getInitParameter("token.exchange.topology.name");
+ currentTopologyName = (String) servletContext.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+ }
+
+ @GET
+ public Response getConfig(@Context UriInfo uriInfo) {
+ final String baseUrl = uriInfo.getBaseUri().toString();
+ final Map config = new HashMap<>();
+ config.put("issuer", baseUrl + "knoxidf");
+ config.put("authorization_endpoint", baseUrl + AuthorizeResource.RESOURCE_PATH);
+ String tokenEndpoint = baseUrl + TokenResource.RESOURCE_PATH;
+ String userInfoEndpoint = baseUrl + UserInfoResource.RESOURCE_PATH;
+ if (tokenExchangeTopologyName != null) {
+ tokenEndpoint = tokenEndpoint.replaceAll(currentTopologyName, tokenExchangeTopologyName);
+ userInfoEndpoint = userInfoEndpoint.replaceAll(currentTopologyName, tokenExchangeTopologyName);
+ }
+ config.put("token_endpoint", tokenEndpoint);
+ config.put("userinfo_endpoint", userInfoEndpoint);
+ config.put("jwks_uri", baseUrl + JwksResource.RESOURCE_PATH);
+ config.put("response_types_supported", new String[]{KnoxIDFConstants.CODE});
+ config.put("grant_types_supported", new String[]{KnoxIDFConstants.AUTH_CODE, KnoxIDFConstants.REFRESH_TOKEN});
+ config.put("scopes_supported", KnoxIDFConstants.DEFAULT_SCOPES);
+ config.put("id_token_signing_alg_values_supported", new String[]{"RS256"});
+ return Response.ok(JsonUtils.renderAsJsonString(config)).build();
+ }
+
+}
diff --git a/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/JwksResource.java b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/JwksResource.java
new file mode 100644
index 0000000000..47f802b04d
--- /dev/null
+++ b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/JwksResource.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.service.knoxidf;
+
+import org.apache.knox.gateway.service.knoxtoken.JWKSResource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import static org.apache.knox.gateway.util.knoxidf.KnoxIDFConstants.BASE_RESORCE_PATH;
+
+@Path(JwksResource.RESOURCE_PATH)
+@Produces(MediaType.APPLICATION_JSON)
+public class JwksResource extends JWKSResource {
+ static final String RESOURCE_PATH = BASE_RESORCE_PATH + "/jwks";
+
+ @GET
+ public Response getKeys() {
+ return getJwksResponse();
+ }
+}
+
diff --git a/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/LdapUserParamsProvider.java b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/LdapUserParamsProvider.java
new file mode 100644
index 0000000000..0bd567c3fd
--- /dev/null
+++ b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/LdapUserParamsProvider.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.service.knoxidf;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+public enum OIDCScope {
+ OPENID(new HashSet<>(Collections.singletonList("sub"))),
+ PROFILE(new HashSet<>(Arrays.asList(
+ "name", "family_name", "given_name",
+ "middle_name", "nickname", "preferred_username",
+ "profile", "picture", "website", "gender",
+ "birthdate", "zoneinfo", "locale", "updated_at"
+ ))),
+ EMAIL(new HashSet<>(Arrays.asList("email", "email_verified"))),
+ ADDRESS(new HashSet<>(Collections.singletonList("address"))),
+ PHONE(new HashSet<>(Arrays.asList("phone_number", "phone_number_verified"))),
+ ROLES(new HashSet<>(Collections.singletonList("roles"))); // custom extension
+
+ private final Set claims;
+
+ OIDCScope(Set claims) {
+ this.claims = Collections.unmodifiableSet(new HashSet<>(claims));
+ }
+
+ public Set getClaims() {
+ return claims;
+ }
+
+ public static Set claimsForScopes(String scopeString) {
+ Set result = new HashSet<>();
+ if (StringUtils.isEmpty(scopeString)) {
+ return result;
+ }
+
+ for (String s : scopeString.split("\\s+")) {
+ try {
+ OIDCScope scope = OIDCScope.valueOf(s.toUpperCase(Locale.US));
+ result.addAll(scope.getClaims());
+ } catch (IllegalArgumentException ignored) {
+ // ignore unknown scopes
+ }
+ }
+ return result;
+ }
+}
diff --git a/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/RegistrationResource.java b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/RegistrationResource.java
new file mode 100644
index 0000000000..05675e4917
--- /dev/null
+++ b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/RegistrationResource.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.service.knoxidf;
+
+import java.util.Map;
+
+public interface UserParamsProvider {
+
+ /**
+ * Fetches OIDC parameters for the given subject name.
+ *
+ * @param subjectName The user login/ID (e.g., "sam").
+ * @return a map of OIDC parameters (e.g., email, name, roles)
+ */
+ Map getParamsFor(String subjectName, String scope);
+}
diff --git a/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/deploy/KnoxIDFServiceDeploymentContributor.java b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/deploy/KnoxIDFServiceDeploymentContributor.java
new file mode 100644
index 0000000000..cfbb4647cb
--- /dev/null
+++ b/gateway-service-knoxidf/src/main/java/org/apache/knox/gateway/service/knoxidf/deploy/KnoxIDFServiceDeploymentContributor.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.services.knoxidf.federation;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public final class FederatedIdentity {
+
+ private final String id;
+ private final String userId;
+ private final String provider;
+ private final String externalSubject;
+ private final String externalIssuer;
+ private final Instant createdAt;
+ private final Map attributes = new HashMap<>();
+
+ public FederatedIdentity(String userId, String provider, String externalSubject, String externalIssuer,
+ Instant createdAt, Map attributes) {
+ this(UUID.randomUUID().toString(), userId, provider, externalSubject, externalIssuer, createdAt, attributes);
+ }
+
+ public FederatedIdentity(String id, String userId, String provider, String externalSubject, String externalIssuer,
+ Instant createdAt, Map attributes) {
+ this.id = id;
+ this.userId = userId;
+ this.provider = provider;
+ this.externalSubject = externalSubject;
+ this.externalIssuer = externalIssuer;
+ this.createdAt = createdAt;
+ if (attributes != null) {
+ this.attributes.putAll(attributes);
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getProvider() {
+ return provider;
+ }
+
+ public String getExternalSubject() {
+ return externalSubject;
+ }
+
+ public String getExternalIssuer() {
+ return externalIssuer;
+ }
+
+ public Instant getCreatedAt() {
+ return createdAt;
+ }
+
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ public String getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+ public void addAttribute(String key, String value) {
+ attributes.put(key, value);
+ }
+}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityService.java
new file mode 100644
index 0000000000..778b589a24
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityService.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.services.knoxidf.federation;
+
+import org.apache.knox.gateway.services.Service;
+
+import java.util.Optional;
+
+public interface FederatedIdentityService extends Service {
+
+ void addFederatedIdentity(FederatedIdentity identity);
+
+ Optional findById(String identityId);
+
+ Optional findByProviderAndSubject(
+ String provider,
+ String externalIssuer,
+ String externalSubject);
+}
+
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityServiceException.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityServiceException.java
new file mode 100644
index 0000000000..30cbce7b24
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/FederatedIdentityServiceException.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.util.knoxidf;
+
+public class AuthorizeRequestMetadataStore extends KnoxIDFArtifactStore{
+
+ private static AuthorizeRequestMetadataStore instance;
+
+ private AuthorizeRequestMetadataStore(long ttl) {
+ super(ttl);
+ }
+
+ public static synchronized AuthorizeRequestMetadataStore getInstance(long ttl) {
+ if (instance == null) {
+ instance = new AuthorizeRequestMetadataStore(ttl);
+ }
+ return instance;
+ }
+}
diff --git a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfiguration.java b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfiguration.java
new file mode 100644
index 0000000000..7ad6078433
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfiguration.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.util.knoxidf;
+
+import javax.servlet.ServletContext;
+
+public class FederatedOpConfiguration {
+ private final boolean enabled;
+ private final String name;
+ private final String clientId;
+ private final String clientSecret;
+ private final String tokenEndpoint;
+ private final String authorizeEndpoint;
+ private final String userInfoEndpoint;
+ private final String discoveryEndpoint;
+ private final String authorizeCallback;
+
+ public FederatedOpConfiguration(final ServletContext servletContext, final String opName) {
+ this.name = opName;
+ final String prefix = KnoxIDFConstants.FEDERATED_OP_CONFIG_PREFIX + (opName != null ? opName + "." : "");
+ this.enabled = Boolean.parseBoolean(servletContext.getInitParameter(prefix + "enabled"));
+ this.clientId = servletContext.getInitParameter(prefix + "clientId");
+ this.clientSecret = servletContext.getInitParameter(prefix + "clientSecret");
+ this.tokenEndpoint = servletContext.getInitParameter(prefix + "token.endpoint");
+ this.authorizeEndpoint = servletContext.getInitParameter(prefix + "authorize.endpoint");
+ this.authorizeCallback = servletContext.getInitParameter(prefix + "authorize.callback");
+ this.userInfoEndpoint = servletContext.getInitParameter(prefix + "userinfo.endpoint");
+ this.discoveryEndpoint = servletContext.getInitParameter(prefix + "discovery.endpoint");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ String getAuthorizeEndpoint() {
+ return authorizeEndpoint;
+ }
+
+ public String getAuthorizeCallback() {
+ return authorizeCallback;
+ }
+
+ public String getTokenEndpoint() {
+ return tokenEndpoint;
+ }
+
+ public String getUserInfoEndpoint() {
+ return userInfoEndpoint;
+ }
+
+ public String getDiscoveryEndpoint() {
+ return discoveryEndpoint;
+ }
+
+}
diff --git a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfigurationFactory.java b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfigurationFactory.java
new file mode 100644
index 0000000000..f1efc3d75b
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfigurationFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.util.knoxidf;
+
+import javax.servlet.ServletContext;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FederatedOpConfigurationFactory {
+
+ public static Map createFederatedOpConfiguration(final ServletContext servletContext) {
+ final String names = servletContext.getInitParameter(KnoxIDFConstants.FEDERATED_OP_CONFIG_NAMES);
+ if (names == null || names.isEmpty()) {
+ return Collections.emptyMap();
+ }
+
+ final Map configs = new HashMap<>();
+ for (String name : names.split(",")) {
+ final String trimmedName = name.trim();
+ final FederatedOpConfiguration federatedOpConfiguration = new FederatedOpConfiguration(servletContext, trimmedName);
+ if (federatedOpConfiguration.isEnabled()) {
+ configs.put(trimmedName, federatedOpConfiguration);
+ }
+ }
+ return configs;
+ }
+}
diff --git a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfigurationStore.java b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfigurationStore.java
new file mode 100644
index 0000000000..80bbb1a04f
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/FederatedOpConfigurationStore.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.util.knoxidf;
+
+import java.util.Set;
+
+public class FederatedOpConfigurationStore extends KnoxIDFArtifactStore> {
+
+ private static FederatedOpConfigurationStore instance;
+
+ private FederatedOpConfigurationStore(long ttl) {
+ super(ttl);
+ }
+
+ public static synchronized FederatedOpConfigurationStore getInstance(long ttl) {
+ if (instance == null) {
+ instance = new FederatedOpConfigurationStore(ttl);
+ }
+ return instance;
+ }
+}
diff --git a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFArtifactStore.java b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFArtifactStore.java
new file mode 100644
index 0000000000..dbeba8141d
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFArtifactStore.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.util.knoxidf;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import java.util.concurrent.TimeUnit;
+
+public abstract class KnoxIDFArtifactStore {
+
+ private final Cache cache;
+
+ protected KnoxIDFArtifactStore(long ttl) {
+ this.cache = Caffeine.newBuilder().expireAfterWrite(ttl * 2, TimeUnit.MILLISECONDS).build();
+ }
+
+ public void put(String key, T value) {
+ cache.put(key, value);
+ }
+
+ public T get(String key) {
+ return cache.getIfPresent(key);
+ }
+}
diff --git a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFConstants.java b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFConstants.java
new file mode 100644
index 0000000000..6526a54e3d
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFConstants.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.util.knoxidf;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+public interface KnoxIDFConstants {
+ String BASE_RESORCE_PATH = "knoxidf/api/v1";
+ String AUTH_CODE = "authorization_code";
+ String CLIENT_ID = "client_id";
+ String REDIRECT_URI = "redirect_uri";
+ String RESPONSE_TYPE = "response_type";
+ Set ALLOWED_RESPONSE_TYPES = Sets.newHashSet("code", "id_token", "code id_token");
+ String SCOPE = "scope";
+ String OFFLINE_ACCESS_SCOPE = "offline_access";
+ Set DEFAULT_SCOPES = Sets.newHashSet("openid", "profile", "email", OFFLINE_ACCESS_SCOPE);
+ String OPENID_SCOPE = SCOPE + "=openid";
+ String STATE = "state";
+ String CODE = "code";
+ String REFRESH_TOKEN = "refresh_token";
+ String REFRESH_TOKEN_TTL= "refresh.token.ttl";
+ long REFRESH_TOKEN_TTL_DEFAULT = 86400000L; // 1 day
+ String CODE_RESPONSE_TYPE = RESPONSE_TYPE + "=" + CODE;
+ String NONCE = "nonce";
+
+ String TOKEN_ID_ATTRIBUTE = "X-Token-Id";
+ String SCOPE_ATTRIBUTE = "X-Token-Scope";
+
+ String FEDERATED_ID_TOKEN_PREFIX = "fed_id_";
+ String FEDERATED_ACCESS_TOKEN_PREFIX = "fed_access_";
+ String FEDERATED_OP_CONFIG_PREFIX = "federated.op.";
+ String FEDERATED_OP_CONFIG_NAMES = FEDERATED_OP_CONFIG_PREFIX + "names";
+}
diff --git a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFUtils.java b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFUtils.java
new file mode 100644
index 0000000000..432bf7a195
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/knoxidf/KnoxIDFUtils.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *