aboutsummaryrefslogtreecommitdiffstats
path: root/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war
diff options
context:
space:
mode:
authorKartik Hegde <kh00735564@techmahindra.com>2022-11-12 14:29:11 +0530
committerKartik Hegde <kh00735564@techmahindra.com>2022-12-21 12:04:30 +0000
commitcf04a1a714ef4a1df973929dc750232b4d67d7b4 (patch)
treeedd2e6e5074ba58efb98663a1ea1851208895fff /openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war
parentf995db01ee95606b6cded82822a73435ebc190c8 (diff)
Multitenancy in SDC
Issue-ID: SDC-4215 Change-Id: Ie24ba38acc9f1998d4a7e722e8f98456dab9201d Signed-off-by: Kartik Hegde <kh00735564@techmahindra.com>
Diffstat (limited to 'openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war')
-rw-r--r--openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/java/org/openecomp/server/filters/MultitenancyKeycloakFilter.java286
-rw-r--r--openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/keycloak.json11
-rw-r--r--openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/web.xml14
3 files changed, 311 insertions, 0 deletions
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/java/org/openecomp/server/filters/MultitenancyKeycloakFilter.java b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/java/org/openecomp/server/filters/MultitenancyKeycloakFilter.java
new file mode 100644
index 0000000000..8cb87e3e33
--- /dev/null
+++ b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/java/org/openecomp/server/filters/MultitenancyKeycloakFilter.java
@@ -0,0 +1,286 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2022 Tech-Mahindra Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.server.filters;
+
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.servlet.FilterRequestAuthenticator;
+import org.keycloak.adapters.servlet.OIDCFilterSessionStore;
+import org.keycloak.adapters.servlet.OIDCServletHttpFacade;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.spi.UserSessionManagement;
+import org.openecomp.sdc.common.util.Multitenancy;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+
+
+public class MultitenancyKeycloakFilter implements Filter {
+
+ private static final Logger log = Logger.getLogger("" + MultitenancyKeycloakFilter.class);
+
+ public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern";
+
+ public static final String ID_MAPPER_PARAM = "keycloak.config.idMapper";
+
+ public static final String CONFIG_RESOLVER_PARAM = "keycloak.config.resolver";
+
+ public static final String CONFIG_FILE_PARAM = "keycloak.config.file";
+
+ public static final String CONFIG_PATH_PARAM = "keycloak.config.path";
+
+ protected AdapterDeploymentContext deploymentContext;
+
+ protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
+
+ protected NodesRegistrationManagement nodesRegistrationManagement;
+
+ protected Pattern skipPattern;
+
+ private final KeycloakConfigResolver definedconfigResolver;
+
+ boolean keycloak;
+
+ /**
+ * Constructor that can be used to define a {@code KeycloakConfigResolver} that will be used at initialization to
+ * provide the {@code KeycloakDeployment}.
+ * @param definedconfigResolver the resolver
+ */
+ public MultitenancyKeycloakFilter(KeycloakConfigResolver definedconfigResolver) {
+ this.definedconfigResolver = definedconfigResolver;
+ }
+
+ public MultitenancyKeycloakFilter() {
+ this(null);
+ }
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM);
+ if (skipPatternDefinition != null) {
+ skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
+ }
+
+ String idMapperClassName = filterConfig.getInitParameter(ID_MAPPER_PARAM);
+ if (idMapperClassName != null) {
+ try {
+ final Class<?> idMapperClass = getClass().getClassLoader().loadClass(idMapperClassName);
+ final Constructor<?> idMapperConstructor = idMapperClass.getDeclaredConstructor();
+ Object idMapperInstance = null;
+ // for KEYCLOAK-13745 test
+ if (idMapperConstructor.getModifiers() == Modifier.PRIVATE) {
+ idMapperInstance = idMapperClass.getMethod("getInstance").invoke(null);
+ } else {
+ idMapperInstance = idMapperConstructor.newInstance();
+ }
+ if(idMapperInstance instanceof SessionIdMapper) {
+ this.idMapper = (SessionIdMapper) idMapperInstance;
+ } else {
+ log.log(Level.WARNING, "SessionIdMapper class {0} is not instance of org.keycloak.adapters.spi.SessionIdMapper", idMapperClassName);
+ }
+ } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ log.log(Level.WARNING, "SessionIdMapper class could not be instanced", e);
+ }
+ }
+
+ if (definedconfigResolver != null) {
+ deploymentContext = new AdapterDeploymentContext(definedconfigResolver);
+ log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
+ } else {
+ String configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
+ if (configResolverClass != null) {
+ try {
+ KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).getDeclaredConstructor().newInstance();
+ deploymentContext = new AdapterDeploymentContext(configResolver);
+ log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
+ } catch (Exception ex) {
+ log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
+ deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+ }
+ } else {
+ String fp = filterConfig.getInitParameter(CONFIG_FILE_PARAM);
+ InputStream is = null;
+ if (fp != null) {
+ try {
+ is = new FileInputStream(fp);
+ } catch (FileNotFoundException e) {
+ log.log(Level.FINE, "config file is empty",e);
+ }
+ } else {
+ String path = "/WEB-INF/keycloak.json";
+ String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM);
+ if (pathParam != null) path = pathParam;
+ is = filterConfig.getServletContext().getResourceAsStream(path);
+ }
+ KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
+ deploymentContext = new AdapterDeploymentContext(kd);
+ log.fine("Keycloak is using a per-deployment configuration.");
+ }
+ }
+ filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
+ nodesRegistrationManagement = new NodesRegistrationManagement();
+ }
+
+ private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
+ if (is == null) {
+ log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+ return new KeycloakDeployment();
+ }
+ return KeycloakDeploymentBuilder.build(is);
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+ log.fine("Keycloak OIDC Filter");
+ Multitenancy keyaccess= new Multitenancy();
+ keycloak= keyaccess.multiTenancyCheck();
+
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+
+ if (!keycloak) {
+ chain.doFilter(req, res);
+ return;
+ }
+
+ if (shouldSkip(request)) {
+ chain.doFilter(req, res);
+ return;
+ }
+
+ OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (deployment == null || !deployment.isConfigured()) {
+ response.sendError(403);
+ log.fine("deployment not configured");
+ return;
+ }
+
+ PreAuthActionsHandler preActions = new PreAuthActionsHandler(new org.openecomp.server.filters.MultitenancyKeycloakFilter.IdMapperUserSessionManagement(), deploymentContext, facade);
+
+ if (preActions.handleRequest()) {
+ return;
+ }
+
+
+ nodesRegistrationManagement.tryRegister(deployment);
+ OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(request, facade, 100000, deployment, idMapper);
+ tokenStore.checkCurrentToken();
+
+
+ FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(deployment, tokenStore, facade, request, 8443);
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ log.fine("AUTHENTICATED");
+ if (facade.isEnded()) {
+ return;
+ }
+ AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, facade);
+ if (actions.handledRequest()) {
+ return;
+ } else {
+ HttpServletRequestWrapper wrapper = tokenStore.buildWrapper();
+ chain.doFilter(wrapper, res);
+ return;
+ }
+ }
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ log.fine("challenge");
+ challenge.challenge(facade);
+ return;
+ }
+ response.sendError(403);
+
+ }
+
+ /**
+ * Decides whether this {@link Filter} should skip the given {@link HttpServletRequest} based on the configured {@link org.keycloak.adapters.servlet.KeycloakOIDCFilter#skipPattern}.
+ * Patterns are matched against the {@link HttpServletRequest#getRequestURI() requestURI} of a request without the context-path.
+ * A request for {@code /myapp/index.html} would be tested with {@code /index.html} against the skip pattern.
+ * Skipped requests will not be processed further by {@link org.keycloak.adapters.servlet.KeycloakOIDCFilter} and immediately delegated to the {@link FilterChain}.
+ *
+ * @param request the request to check
+ * @return {@code true} if the request should not be handled,
+ * {@code false} otherwise.
+ */
+ private boolean shouldSkip(HttpServletRequest request) {
+
+ if (skipPattern == null) {
+ return false;
+ }
+
+ String requestPath = request.getRequestURI().substring(request.getContextPath().length());
+ return skipPattern.matcher(requestPath).matches();
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ private class IdMapperUserSessionManagement implements UserSessionManagement {
+ @Override
+ public void logoutAll() {
+ if (idMapper != null) {
+ idMapper.clear();
+ }
+ }
+
+ @Override
+ public void logoutHttpSessions(List<String> ids) {
+ log.fine("**************** logoutHttpSessions");
+ for (String id : ids) {
+ log.finest(id);
+ idMapper.removeSession(id);
+ }
+
+ }
+ }
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/keycloak.json b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/keycloak.json
new file mode 100644
index 0000000000..d037661aec
--- /dev/null
+++ b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,11 @@
+{
+"realm": "sdc",
+"auth-server-url": "http://10.32.243.37:31613/",
+"ssl-required": "external",
+"resource": "sdc-app",
+"public-client":true,
+"bearer-only":true,
+"use-resource-role-mappings": true,
+"principal-attribute":"preferred_username",
+"confidential-port": 0
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/web.xml b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/web.xml
index 31400f878e..7d2edf4994 100644
--- a/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/web.xml
+++ b/openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/web.xml
@@ -24,6 +24,20 @@
<listener-class>org.openecomp.server.listeners.OnboardingAppStartupListener</listener-class>
</listener>
+ <!--KEYCLOAK FILTER -->
+ <filter>
+ <filter-name>Keycloak Filter</filter-name>
+ <filter-class>org.openecomp.server.filters.MultitenancyKeycloakFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>Keycloak Filter</filter-name>
+ <url-pattern>/keycloak/*</url-pattern>
+ <url-pattern>/v1.0/vendor-license-models/*</url-pattern>
+ <url-pattern>/v1.0/vendor-software-products</url-pattern>
+ <url-pattern>*/actions</url-pattern>
+ <url-pattern>/v1.0/items/*</url-pattern>
+ </filter-mapping>
+
<filter>
<filter-name>dataValidatorFilter</filter-name>
<filter-class>org.openecomp.sdc.common.filters.DataValidatorFilter</filter-class>