From 03b16f6df6fe5545a4ed219916c77dd696694ea2 Mon Sep 17 00:00:00 2001 From: Tomek Kaminski Date: Wed, 3 Apr 2019 09:45:09 +0200 Subject: [PATCH] CADI authentication and authorization filters Implemented AAF CADI usage for authN/authZ Old solution left for backward compatibility UseAAF flag used to turn on/of CADI and AAF permissions AAfConnection fixed to accept AAF server certificate when adding perm Change-Id: I8e946bda14c53e57c3236f2a7dfe806bcd45e519 Issue-ID: DMAAP-1112 Signed-off-by: Tomek Kaminski --- .../org/onap/dmaap/dbcapi/aaf/AafConnection.java | 28 ++++ .../dbcapi/resources/AAFAuthenticationFilter.java | 128 +++++++++++++++ .../dbcapi/resources/AAFAuthorizationFilter.java | 116 ++++++++++++++ .../dbcapi/resources/AuthorizationFilter.java | 49 +++--- .../org/onap/dmaap/dbcapi/server/JettyServer.java | 174 ++++++++++---------- .../onap/dmaap/dbcapi/util/PermissionBuilder.java | 86 ++++++++++ .../resources/AAFAuthenticationFilterTest.java | 178 +++++++++++++++++++++ .../resources/AAFAuthorizationFilterTest.java | 172 ++++++++++++++++++++ .../dmaap/dbcapi/util/PermissionBuilderTest.java | 151 +++++++++++++++++ 9 files changed, 981 insertions(+), 101 deletions(-) create mode 100644 src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilter.java create mode 100644 src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilter.java create mode 100644 src/main/java/org/onap/dmaap/dbcapi/util/PermissionBuilder.java create mode 100644 src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilterTest.java create mode 100644 src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilterTest.java create mode 100644 src/test/java/org/onap/dmaap/dbcapi/util/PermissionBuilderTest.java diff --git a/src/main/java/org/onap/dmaap/dbcapi/aaf/AafConnection.java b/src/main/java/org/onap/dmaap/dbcapi/aaf/AafConnection.java index e22290a..934e541 100644 --- a/src/main/java/org/onap/dmaap/dbcapi/aaf/AafConnection.java +++ b/src/main/java/org/onap/dmaap/dbcapi/aaf/AafConnection.java @@ -37,8 +37,11 @@ import java.net.UnknownHostException; import java.net.ConnectException; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import org.apache.commons.codec.binary.Base64; import org.onap.dmaap.dbcapi.logging.BaseLoggingClass; import org.onap.dmaap.dbcapi.logging.DmaapbcLogMessageEnum; @@ -129,6 +132,10 @@ public class AafConnection extends BaseLoggingClass { uc.setRequestProperty( "Content-Length", Integer.toString( postData.length )); uc.setUseCaches(false); uc.setDoOutput(true); + + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + uc.setSSLSocketFactory(sc.getSocketFactory()); OutputStream os = null; @@ -296,6 +303,27 @@ public class AafConnection extends BaseLoggingClass { return rc; } + + private TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() + { + return null; + } + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + //No need to implement. + } + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + //No need to implement. + } + } + }; } diff --git a/src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilter.java b/src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilter.java new file mode 100644 index 0000000..8739511 --- /dev/null +++ b/src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilter.java @@ -0,0 +1,128 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia 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.onap.dmaap.dbcapi.resources; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +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.HttpServletResponse; +import org.apache.log4j.Logger; +import org.eclipse.jetty.http.HttpStatus; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.filter.CadiFilter; +import org.onap.dmaap.dbcapi.model.ApiError; +import org.onap.dmaap.dbcapi.util.DmaapConfig; + +public class AAFAuthenticationFilter implements Filter { + + private static final Logger LOGGER = Logger.getLogger(AAFAuthenticationFilter.class.getName()); + static final String CADI_PROPERTIES = "cadi.properties"; + static final String AAF_AUTHN_FLAG = "UseAAF"; + + private boolean isAafEnabled; + private CadiFilter cadiFilter; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + DmaapConfig dmaapConfig = getConfig(); + String flag = dmaapConfig.getProperty(AAF_AUTHN_FLAG, "false"); + isAafEnabled = "true".equalsIgnoreCase(flag); + initCadi(dmaapConfig); + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + + if(isAafEnabled) { + cadiFilter.doFilter(servletRequest, servletResponse, filterChain); + updateResponseBody((HttpServletResponse)servletResponse); + } else { + filterChain.doFilter(servletRequest, servletResponse); + } + } + + private void updateResponseBody(HttpServletResponse httpResponse) + throws IOException { + if(httpResponse.getStatus() == 401) { + String errorMsg = "invalid or no credentials provided"; + LOGGER.error(errorMsg); + httpResponse.setContentType("application/json"); + httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.getWriter().print(buildErrorResponse(errorMsg)); + httpResponse.getWriter().flush(); + } + } + + private String buildErrorResponse(String msg) { + try { + return new ObjectMapper().writeValueAsString(new ApiError(HttpStatus.UNAUTHORIZED_401, msg, "Authentication")); + } catch (JsonProcessingException e) { + LOGGER.warn("Could not serialize response entity: " + e.getMessage()); + return ""; + } + } + + + @Override + public void destroy() { + //nothing to cleanup + } + + private void initCadi(DmaapConfig dmaapConfig) throws ServletException { + if(isAafEnabled) { + try { + String cadiPropertiesFile = dmaapConfig.getProperty(CADI_PROPERTIES); + if(cadiPropertiesFile != null && !cadiPropertiesFile.isEmpty()) { + cadiFilter = new CadiFilter(new PropAccess(cadiPropertiesFile)); + } else { + throw new ServletException("Cannot initialize CADI filter.CADI properties not available."); + } + } catch (ServletException e) { + LOGGER.error("CADI init error :" + e.getMessage()); + throw e; + } + } + } + + DmaapConfig getConfig() { + return (DmaapConfig) DmaapConfig.getConfig(); + } + + //tests only + CadiFilter getCadiFilter() { + return cadiFilter; + } + + void setCadiFilter(CadiFilter cadiFilter) { + this.cadiFilter = cadiFilter; + } + + boolean isAafEnabled() { + return isAafEnabled; + } +} diff --git a/src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilter.java b/src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilter.java new file mode 100644 index 0000000..5bc3dec --- /dev/null +++ b/src/main/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilter.java @@ -0,0 +1,116 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia 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.onap.dmaap.dbcapi.resources; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +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.HttpServletResponse; +import org.apache.log4j.Logger; +import org.eclipse.jetty.http.HttpStatus; +import org.onap.dmaap.dbcapi.model.ApiError; +import org.onap.dmaap.dbcapi.service.DmaapService; +import org.onap.dmaap.dbcapi.util.DmaapConfig; +import org.onap.dmaap.dbcapi.util.PermissionBuilder; + +public class AAFAuthorizationFilter implements Filter{ + + private static final Logger LOGGER = Logger.getLogger(AAFAuthenticationFilter.class.getName()); + static final String AAF_AUTHZ_FLAG = "UseAAF"; + private boolean isAafEnabled = false; + + private PermissionBuilder permissionBuilder; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + DmaapConfig dmaapConfig = getConfig(); + isAafEnabled = "true".equalsIgnoreCase(dmaapConfig.getProperty(AAF_AUTHZ_FLAG, "false")); + if(isAafEnabled) { + permissionBuilder = new PermissionBuilder(dmaapConfig, getDmaapService()); + } + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + + if(isAafEnabled) { + HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; + permissionBuilder.updateDmaapInstance(); + String permission = permissionBuilder.buildPermission(httpRequest); + + if (httpRequest.isUserInRole(permission)) { + LOGGER.info("User " + httpRequest.getUserPrincipal().getName() + " has permission " + permission); + filterChain.doFilter(servletRequest, servletResponse); + } else { + String msg = "User " + httpRequest.getUserPrincipal().getName() + " does not have permission " + permission; + LOGGER.error(msg); + ((HttpServletResponse) servletResponse).setStatus(HttpStatus.FORBIDDEN_403); + servletResponse.setContentType("application/json"); + servletResponse.setCharacterEncoding("UTF-8"); + servletResponse.getWriter().print(buildErrorResponse(msg)); + servletResponse.getWriter().flush(); + } + } else { + filterChain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void destroy() { + //nothing to cleanup + } + + DmaapConfig getConfig() { + return (DmaapConfig) DmaapConfig.getConfig(); + } + + DmaapService getDmaapService() { + return new DmaapService(); + } + + private String buildErrorResponse(String msg) { + try { + return new ObjectMapper().writeValueAsString(new ApiError(HttpStatus.FORBIDDEN_403, msg, "Authorization")); + } catch (JsonProcessingException e) { + LOGGER.warn("Could not serialize response entity: " + e.getMessage()); + return ""; + } + } + + PermissionBuilder getPermissionBuilder() { + return permissionBuilder; + } + + void setPermissionBuilder(PermissionBuilder permissionBuilder) { + this.permissionBuilder = permissionBuilder; + } + + void setAafEnabled(boolean aafEnabled) { + isAafEnabled = aafEnabled; + } +} diff --git a/src/main/java/org/onap/dmaap/dbcapi/resources/AuthorizationFilter.java b/src/main/java/org/onap/dmaap/dbcapi/resources/AuthorizationFilter.java index fd5b4aa..3ed5717 100644 --- a/src/main/java/org/onap/dmaap/dbcapi/resources/AuthorizationFilter.java +++ b/src/main/java/org/onap/dmaap/dbcapi/resources/AuthorizationFilter.java @@ -26,33 +26,44 @@ import javax.ws.rs.container.ContainerRequestFilter; import org.apache.log4j.Logger; import org.onap.dmaap.dbcapi.authentication.AuthenticationErrorException; import org.onap.dmaap.dbcapi.service.ApiService; +import org.onap.dmaap.dbcapi.util.DmaapConfig; @Authorization public class AuthorizationFilter implements ContainerRequestFilter { - - private Logger logger = Logger.getLogger(AuthorizationFilter.class.getName()); - private ResponseBuilder responseBuilder = new ResponseBuilder(); - + + private static final String AAF_FLAG = "UseAAF"; + private final Logger logger = Logger.getLogger(AuthorizationFilter.class.getName()); + private final ResponseBuilder responseBuilder = new ResponseBuilder(); + private final boolean isAafEnabled; + + + public AuthorizationFilter() { + DmaapConfig dmaapConfig = (DmaapConfig) DmaapConfig.getConfig(); + String flag = dmaapConfig.getProperty(AAF_FLAG, "false"); + isAafEnabled = "true".equalsIgnoreCase(flag); + } + @Override public void filter(ContainerRequestContext requestContext) { - ApiService apiResp = new ApiService() - .setAuth( requestContext.getHeaderString("Authorization") ) - .setUriPath(requestContext.getUriInfo().getPath()) - .setHttpMethod( requestContext.getMethod() ) - .setRequestId( requestContext.getHeaderString("X-ECOMP-RequestID") ); - - try { - apiResp.checkAuthorization(); - } catch ( AuthenticationErrorException ae ) { - logger.error("Error", ae); - requestContext.abortWith( responseBuilder.unauthorized( apiResp.getErr().getMessage() ) ); - } catch ( Exception e ) { - logger.error("Error", e); - requestContext.abortWith( responseBuilder.unavailable() ); - } + if(!isAafEnabled) { + ApiService apiResp = new ApiService() + .setAuth(requestContext.getHeaderString("Authorization")) + .setUriPath(requestContext.getUriInfo().getPath()) + .setHttpMethod(requestContext.getMethod()) + .setRequestId(requestContext.getHeaderString("X-ECOMP-RequestID")); + try { + apiResp.checkAuthorization(); + } catch (AuthenticationErrorException ae) { + logger.error("Error", ae); + requestContext.abortWith(responseBuilder.unauthorized(apiResp.getErr().getMessage())); + } catch (Exception e) { + logger.error("Error", e); + requestContext.abortWith(responseBuilder.unavailable()); + } + } } } diff --git a/src/main/java/org/onap/dmaap/dbcapi/server/JettyServer.java b/src/main/java/org/onap/dmaap/dbcapi/server/JettyServer.java index 7f34725..7457ce9 100644 --- a/src/main/java/org/onap/dmaap/dbcapi/server/JettyServer.java +++ b/src/main/java/org/onap/dmaap/dbcapi/server/JettyServer.java @@ -22,7 +22,8 @@ package org.onap.dmaap.dbcapi.server; - +import com.google.common.collect.Sets; +import javax.servlet.DispatcherType; import org.eclipse.jetty.server.*; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -31,6 +32,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.onap.dmaap.dbcapi.logging.BaseLoggingClass; import java.util.Properties; + /** * A Jetty server which supports: * - http and https (simultaneously for dev env) @@ -38,48 +40,47 @@ import java.util.Properties; * - static html pages (for documentation). */ public class JettyServer extends BaseLoggingClass { - private Server server; + private Server server; - public Server getServer() { - return server; - } - public JettyServer( Properties params ) throws Exception { + public Server getServer() { + return server; + } + + public JettyServer(Properties params) throws Exception { server = new Server(); - int httpPort = Integer.valueOf(params.getProperty("IntHttpPort", "80" )); - int sslPort = Integer.valueOf(params.getProperty("IntHttpsPort", "443" )); - boolean allowHttp = Boolean.valueOf(params.getProperty("HttpAllowed", "false")); - serverLogger.info( "port params: http=" + httpPort + " https=" + sslPort ); - serverLogger.info( "allowHttp=" + allowHttp ); - - // HTTP Server - HttpConfiguration http_config = new HttpConfiguration(); - http_config.setSecureScheme("https"); - http_config.setSecurePort(sslPort); - http_config.setOutputBufferSize(32768); - - - - try(ServerConnector httpConnector = new ServerConnector(server, new HttpConnectionFactory(http_config))) { - httpConnector.setPort(httpPort); - httpConnector.setIdleTimeout(30000); - - - // HTTPS Server - - HttpConfiguration https_config = new HttpConfiguration(http_config); - https_config.addCustomizer(new SecureRequestCustomizer()); - SslContextFactory sslContextFactory = new SslContextFactory(); - - setUpKeystore(params, sslContextFactory); - setUpTrustStore(params, sslContextFactory); - - if (sslPort != 0) { - try(ServerConnector sslConnector = new ServerConnector(server, - new SslConnectionFactory(sslContextFactory, "http/1.1"), - new HttpConnectionFactory(https_config))) { + int httpPort = Integer.valueOf(params.getProperty("IntHttpPort", "80")); + int sslPort = Integer.valueOf(params.getProperty("IntHttpsPort", "443")); + boolean allowHttp = Boolean.valueOf(params.getProperty("HttpAllowed", "false")); + serverLogger.info("port params: http=" + httpPort + " https=" + sslPort); + serverLogger.info("allowHttp=" + allowHttp); + + // HTTP Server + HttpConfiguration http_config = new HttpConfiguration(); + http_config.setSecureScheme("https"); + http_config.setSecurePort(sslPort); + http_config.setOutputBufferSize(32768); + + try (ServerConnector httpConnector = new ServerConnector(server, new HttpConnectionFactory(http_config))) { + httpConnector.setPort(httpPort); + httpConnector.setIdleTimeout(30000); + + // HTTPS Server + + HttpConfiguration https_config = new HttpConfiguration(http_config); + https_config.addCustomizer(new SecureRequestCustomizer()); + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setWantClientAuth(true); + + setUpKeystore(params, sslContextFactory); + setUpTrustStore(params, sslContextFactory); + + if (sslPort != 0) { + try (ServerConnector sslConnector = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, "http/1.1"), + new HttpConnectionFactory(https_config))) { sslConnector.setPort(sslPort); if (allowHttp) { logger.info("Starting httpConnector on port " + httpPort); @@ -91,62 +92,71 @@ public class JettyServer extends BaseLoggingClass { server.setConnectors(new Connector[]{sslConnector}); } } - } else { - serverLogger.info("NOT starting sslConnector on port " + sslPort + " for https"); - if (allowHttp) { - serverLogger.info("Starting httpConnector on port " + httpPort); - server.setConnectors(new Connector[]{httpConnector}); - } - } - } + } else { + serverLogger.info("NOT starting sslConnector on port " + sslPort + " for https"); + if (allowHttp) { + serverLogger.info("Starting httpConnector on port " + httpPort); + server.setConnectors(new Connector[]{httpConnector}); + } + } + } // Set context for servlet. This is shared for http and https - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - server.setHandler( context ); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); - ServletHolder jerseyServlet = context.addServlet( org.glassfish.jersey.servlet.ServletContainer.class, "/webapi/*"); + ServletHolder jerseyServlet = context + .addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/webapi/*"); jerseyServlet.setInitOrder(1); - jerseyServlet.setInitParameter("jersey.config.server.provider.packages", "org.onap.dmaap.dbcapi.resources" ); - jerseyServlet.setInitParameter("javax.ws.rs.Application", "org.onap.dmaap.dbcapi.server.ApplicationConfig" ); - + jerseyServlet.setInitParameter("jersey.config.server.provider.packages", "org.onap.dmaap.dbcapi.resources"); + jerseyServlet.setInitParameter("javax.ws.rs.Application", "org.onap.dmaap.dbcapi.server.ApplicationConfig"); + // also serve up some static pages... - ServletHolder staticServlet = context.addServlet(DefaultServlet.class,"/*"); - staticServlet.setInitParameter("resourceBase","www"); - staticServlet.setInitParameter("pathInfoOnly","true"); + ServletHolder staticServlet = context.addServlet(DefaultServlet.class, "/*"); + staticServlet.setInitParameter("resourceBase", "www"); + staticServlet.setInitParameter("pathInfoOnly", "true"); + + registerAuthFilters(context); try { serverLogger.info("Starting jetty server"); - String unit_test = params.getProperty("UnitTest", "No"); + String unit_test = params.getProperty("UnitTest", "No"); serverLogger.info("UnitTest=" + unit_test); - if ( unit_test.equals( "No" ) ) { - server.start(); - server.dumpStdErr(); - server.join(); - } - } catch ( Exception e ) { - errorLogger.error( "Exception " + e ); + if (unit_test.equals("No")) { + server.start(); + server.dumpStdErr(); + server.join(); + } + } catch (Exception e) { + errorLogger.error("Exception " + e); } finally { - server.destroy(); + server.destroy(); } - + + } + + private void registerAuthFilters(ServletContextHandler context) { + context.addFilter("org.onap.dmaap.dbcapi.resources.AAFAuthenticationFilter", "/webapi/*", + Sets.newEnumSet(Sets.newHashSet(DispatcherType.FORWARD, DispatcherType.REQUEST), DispatcherType.class)); + context.addFilter("org.onap.dmaap.dbcapi.resources.AAFAuthorizationFilter", "/webapi/*", + Sets.newEnumSet(Sets.newHashSet(DispatcherType.FORWARD, DispatcherType.REQUEST), DispatcherType.class)); } - private void setUpKeystore(Properties params, SslContextFactory sslContextFactory) { - String keystore = params.getProperty("KeyStoreFile", "etc/keystore"); - logger.info("https Server using keystore at " + keystore); - sslContextFactory.setKeyStorePath(keystore); - sslContextFactory.setKeyStoreType(params.getProperty("KeyStoreType", "jks")); - sslContextFactory.setKeyStorePassword(params.getProperty("KeyStorePassword", "changeit")); - sslContextFactory.setKeyManagerPassword(params.getProperty("KeyPassword", "changeit")); - } - - private void setUpTrustStore(Properties params, SslContextFactory sslContextFactory) { - String truststore = params.getProperty("TrustStoreFile", "etc/org.onap.dmaap-bc.trust.jks"); - logger.info("https Server using truststore at " + truststore); - sslContextFactory.setTrustStorePath(truststore); - sslContextFactory.setTrustStoreType(params.getProperty("TrustStoreType", "jks")); - sslContextFactory.setTrustStorePassword(params.getProperty("TrustStorePassword", "changeit")); - } + private void setUpKeystore(Properties params, SslContextFactory sslContextFactory) { + String keystore = params.getProperty("KeyStoreFile", "etc/keystore"); + logger.info("https Server using keystore at " + keystore); + sslContextFactory.setKeyStorePath(keystore); + sslContextFactory.setKeyStorePassword(params.getProperty("KeyStorePassword", "changeit")); + sslContextFactory.setKeyManagerPassword(params.getProperty("KeyPassword", "changeit")); + } + + private void setUpTrustStore(Properties params, SslContextFactory sslContextFactory) { + String truststore = params.getProperty("TrustStoreFile", "etc/org.onap.dmaap-bc.trust.jks"); + logger.info("https Server using truststore at " + truststore); + sslContextFactory.setTrustStorePath(truststore); + sslContextFactory.setTrustStoreType(params.getProperty("TrustStoreType", "jks")); + sslContextFactory.setTrustStorePassword(params.getProperty("TrustStorePassword", "changeit")); + } } diff --git a/src/main/java/org/onap/dmaap/dbcapi/util/PermissionBuilder.java b/src/main/java/org/onap/dmaap/dbcapi/util/PermissionBuilder.java new file mode 100644 index 0000000..44c94af --- /dev/null +++ b/src/main/java/org/onap/dmaap/dbcapi/util/PermissionBuilder.java @@ -0,0 +1,86 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia 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.onap.dmaap.dbcapi.util; + +import javax.servlet.http.HttpServletRequest; +import org.onap.dmaap.dbcapi.model.Dmaap; +import org.onap.dmaap.dbcapi.service.DmaapService; + +public class PermissionBuilder { + + static final String API_NS_PROP = "ApiNamespace"; + static final String DEFAULT_API_NS = "org.onap.dmaap-bc.api"; + static final String BOOT_INSTANCE = "boot"; + private static final String PERM_SEPARATOR = "|"; + private static final String NS_SEPARATOR = "."; + private DmaapConfig dmaapConfig; + private DmaapService dmaapService; + private String instance; + private String apiNamespace; + + public PermissionBuilder(DmaapConfig dmaapConfig, DmaapService dmaapService) { + this.dmaapConfig = dmaapConfig; + this.dmaapService = dmaapService; + initFields(); + } + + public synchronized void updateDmaapInstance() { + if(instance == null || instance.isEmpty() || instance.equalsIgnoreCase(BOOT_INSTANCE)) { + String dmaapName = getDmaapName(); + instance = (dmaapName == null || dmaapName.isEmpty()) ? BOOT_INSTANCE : dmaapName; + } + } + + public String buildPermission(HttpServletRequest httpRequest) { + + StringBuilder sb = new StringBuilder(apiNamespace); + sb.append(NS_SEPARATOR) + .append(getPermissionType(httpRequest.getPathInfo())) + .append(PERM_SEPARATOR) + .append(instance) + .append(PERM_SEPARATOR) + .append(httpRequest.getMethod()); + return sb.toString(); + } + + + private void initFields() { + apiNamespace = dmaapConfig.getProperty(API_NS_PROP, DEFAULT_API_NS); + updateDmaapInstance(); + } + + private String getDmaapName() { + Dmaap dmaap = dmaapService.getDmaap(); + return ( dmaap != null ) ? dmaap.getDmaapName() : BOOT_INSTANCE; + } + + private String getPermissionType(String pathInfo) { + char pathSeparator = '/'; + String relativePath = (pathInfo.charAt(pathInfo.length()-1) == pathSeparator) ? + pathInfo.substring(0,pathInfo.length()-1) : pathInfo; + + String[] pathSlices = relativePath.split(String.valueOf(pathSeparator)); + return pathSlices[pathSlices.length-1]; + } + + String getInstance() { + return instance; + } +} diff --git a/src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilterTest.java b/src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilterTest.java new file mode 100644 index 0000000..d5ae5fd --- /dev/null +++ b/src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthenticationFilterTest.java @@ -0,0 +1,178 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia 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.onap.dmaap.dbcapi.resources; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; +import org.onap.aaf.cadi.filter.CadiFilter; +import org.onap.dmaap.dbcapi.util.DmaapConfig; + +@RunWith(MockitoJUnitRunner.class) +public class AAFAuthenticationFilterTest { + + @Spy + private AAFAuthenticationFilter filter; + @Mock + private FilterConfig filterConfig; + @Mock + private CadiFilter cadiFilterMock; + @Mock + private HttpServletRequest servletRequest; + @Mock + private HttpServletResponse servletResponse; + @Mock + private FilterChain filterChain; + @Mock + private DmaapConfig dmaapConfig; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setUp() throws Exception { + doReturn(dmaapConfig).when(filter).getConfig(); + } + + @Test + public void init_shouldNotInitializeCADI_whenAafIsNotUsed() throws Exception { + //given + doReturn("false").when(dmaapConfig).getProperty(eq(AAFAuthenticationFilter.AAF_AUTHN_FLAG), anyString()); + + //when + filter.init(filterConfig); + + //then + assertFalse(filter.isAafEnabled()); + assertNull(filter.getCadiFilter()); + } + + @Test + public void doFilter_shouldSkipCADI_whenAafIsNotUsed() throws Exception { + //given + doReturn("false").when(dmaapConfig).getProperty(eq(AAFAuthenticationFilter.AAF_AUTHN_FLAG), anyString()); + filter.init(filterConfig); + filter.setCadiFilter(cadiFilterMock); + + //when + filter.doFilter(servletRequest, servletResponse, filterChain); + + //then + verify(filterChain).doFilter(servletRequest,servletResponse); + verifyZeroInteractions(cadiFilterMock,servletRequest,servletResponse); + } + + @Test + public void init_shouldFail_whenAafIsUsed_andCadiPropertiesHasNotBeenSet() throws Exception { + //given + doReturn("true").when(dmaapConfig).getProperty(eq(AAFAuthenticationFilter.AAF_AUTHN_FLAG), anyString()); + doReturn("").when(dmaapConfig).getProperty(AAFAuthenticationFilter.CADI_PROPERTIES); + + //then + thrown.expect(ServletException.class); + thrown.expectMessage("Cannot initialize CADI filter.CADI properties not available."); + + //when + filter.init(filterConfig); + } + + @Test + public void init_shouldInitializeCADI_whenAafIsUsed_andCadiPropertiesSet() throws Exception { + //given + doReturn("true").when(dmaapConfig).getProperty(eq(AAFAuthenticationFilter.AAF_AUTHN_FLAG), anyString()); + doReturn("cadi.properties").when(dmaapConfig).getProperty(AAFAuthenticationFilter.CADI_PROPERTIES); + + //when + filter.init(filterConfig); + + //then + assertTrue(filter.isAafEnabled()); + assertNotNull(filter.getCadiFilter()); + } + + @Test + public void doFilter_shouldUseCADIfilter_andAuthenticateUser_whenAAFisUsed_andUserIsValid() throws Exception{ + //given + initCADIFilter(); + doReturn(200).when(servletResponse).getStatus(); + + //when + filter.doFilter(servletRequest,servletResponse,filterChain); + + //then + verify(cadiFilterMock).doFilter(servletRequest,servletResponse,filterChain); + verify(servletResponse).getStatus(); + verifyNoMoreInteractions(servletResponse); + verifyZeroInteractions(filterChain, servletRequest); + } + + @Test + public void doFilter_shouldUseCADIfilter_andReturnAuthenticationError_whenAAFisUsed_andUserInvalid() throws Exception{ + //given + String errorResponseJson = "{\"code\":401,\"message\":\"invalid or no credentials provided\",\"fields\":\"Authentication\",\"2xx\":false}"; + initCADIFilter(); + doReturn(401).when(servletResponse).getStatus(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + doReturn(pw).when(servletResponse).getWriter(); + + //when + filter.doFilter(servletRequest,servletResponse,filterChain); + + //then + verify(cadiFilterMock).doFilter(servletRequest,servletResponse,filterChain); + verify(servletResponse).getStatus(); + verify(servletResponse).setContentType("application/json"); + verifyZeroInteractions(filterChain, servletRequest); + assertEquals(errorResponseJson, sw.toString()); + } + + private void initCADIFilter() throws Exception{ + doReturn("true").when(dmaapConfig).getProperty(eq(AAFAuthenticationFilter.AAF_AUTHN_FLAG), anyString()); + doReturn("cadi.properties").when(dmaapConfig).getProperty(AAFAuthenticationFilter.CADI_PROPERTIES); + filter.init(filterConfig); + filter.setCadiFilter(cadiFilterMock); + } + +} \ No newline at end of file diff --git a/src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilterTest.java b/src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilterTest.java new file mode 100644 index 0000000..73794cd --- /dev/null +++ b/src/test/java/org/onap/dmaap/dbcapi/resources/AAFAuthorizationFilterTest.java @@ -0,0 +1,172 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia 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.onap.dmaap.dbcapi.resources; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; +import org.onap.dmaap.dbcapi.model.Dmaap; +import org.onap.dmaap.dbcapi.service.DmaapService; +import org.onap.dmaap.dbcapi.util.DmaapConfig; +import org.onap.dmaap.dbcapi.util.PermissionBuilder; +import sun.security.acl.PrincipalImpl; + +@RunWith(MockitoJUnitRunner.class) +public class AAFAuthorizationFilterTest { + + @Spy + private AAFAuthorizationFilter filter; + @Mock + private FilterConfig filterConfig; + @Mock + private HttpServletRequest servletRequest; + @Mock + private HttpServletResponse servletResponse; + @Mock + private FilterChain filterChain; + @Mock + private DmaapConfig dmaapConfig; + @Mock + private PermissionBuilder permissionBuilder; + @Mock + private DmaapService dmaapService; + + @Before + public void setUp() throws Exception { + filter.setPermissionBuilder(permissionBuilder); + doReturn(dmaapConfig).when(filter).getConfig(); + doReturn(dmaapService).when(filter).getDmaapService(); + } + + @Test + public void init_shouldNotInitializePermissionBuilder_whenAAFnotUsed() throws Exception { + //given + filter.setPermissionBuilder(null); + configureAAFUsage(false); + + //when + filter.init(filterConfig); + + //then + assertNull(filter.getPermissionBuilder()); + } + + @Test + public void init_shouldInitializePermissionBuilder_whenAAFisUsed() throws Exception { + //given + filter.setPermissionBuilder(null); + configureAAFUsage(true); + //doReturn(provideEmptyInstance()).when(dmaapService).getDmaap(); + when(dmaapService.getDmaap()).thenReturn(mock(Dmaap.class)); + + //when + filter.init(filterConfig); + + //then + assertNotNull(permissionBuilder); + } + + @Test + public void doFilter_shouldSkipAuthorization_whenAAFnotUsed() throws Exception { + //given + filter.setAafEnabled(false); + + //when + filter.doFilter(servletRequest,servletResponse,filterChain); + + //then + verify(filterChain).doFilter(servletRequest,servletResponse); + verifyNoMoreInteractions(filterChain); + verifyZeroInteractions(permissionBuilder, servletRequest, servletResponse); + } + + @Test + public void doFilter_shouldPass_whenUserHasPermissionToResourceEndpoint() throws Exception { + //given + String user = "johnny"; + String permission = "org.onap.dmaap-bc.api.topics|mr|GET"; + when(permissionBuilder.buildPermission(servletRequest)).thenReturn(permission); + configureServletRequest(permission, user, true); + filter.setAafEnabled(true); + + //when + filter.doFilter(servletRequest,servletResponse,filterChain); + + //then + verify(filterChain).doFilter(servletRequest,servletResponse); + verify(permissionBuilder).updateDmaapInstance(); + verifyZeroInteractions(servletResponse); + } + + @Test + public void doFilter_shouldReturnError_whenUserDontHavePermissionToResourceEndpoint() throws Exception { + //given + String user = "jack"; + String permission = "org.onap.dmaap-bc.api.topics|mr|GET"; + when(permissionBuilder.buildPermission(servletRequest)).thenReturn(permission); + configureServletRequest(permission, user, false); + filter.setAafEnabled(true); + + String errorMsgJson = "{\"code\":403,\"message\":\"User "+user+" does not have permission " + + permission +"\",\"fields\":\"Authorization\",\"2xx\":false}"; + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + when(servletResponse.getWriter()).thenReturn(pw); + + //when + filter.doFilter(servletRequest,servletResponse,filterChain); + + //then + verifyZeroInteractions(filterChain); + verify(permissionBuilder).updateDmaapInstance(); + verify(servletResponse).setStatus(403); + assertEquals(errorMsgJson, sw.toString()); + } + + private void configureServletRequest(String permission, String user, boolean isUserInRole) { + when(servletRequest.getUserPrincipal()).thenReturn(new PrincipalImpl(user)); + when(servletRequest.isUserInRole(permission)).thenReturn(isUserInRole); + } + + private void configureAAFUsage(Boolean isUsed) { + doReturn(isUsed.toString()).when(dmaapConfig).getProperty(eq(AAFAuthorizationFilter.AAF_AUTHZ_FLAG), anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/org/onap/dmaap/dbcapi/util/PermissionBuilderTest.java b/src/test/java/org/onap/dmaap/dbcapi/util/PermissionBuilderTest.java new file mode 100644 index 0000000..1858e47 --- /dev/null +++ b/src/test/java/org/onap/dmaap/dbcapi/util/PermissionBuilderTest.java @@ -0,0 +1,151 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia 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.onap.dmaap.dbcapi.util; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import javax.servlet.http.HttpServletRequest; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.onap.dmaap.dbcapi.model.Dmaap; +import org.onap.dmaap.dbcapi.model.DmaapObject.DmaapObject_Status; +import org.onap.dmaap.dbcapi.service.DmaapService; + +@RunWith(MockitoJUnitRunner.class) +public class PermissionBuilderTest { + + private static final String DMAAP_NAME = "mr"; + private PermissionBuilder permissionBuilder; + @Mock + private DmaapConfig dmaapConfig; + @Mock + private DmaapService dmaapService; + @Mock + private HttpServletRequest request; + + + @Test + public void updateDmaapInstance_shouldSetBootInstance_whenDmaapIsNotInitialized() { + //given + doReturn(null).when(dmaapService).getDmaap(); + permissionBuilder = new PermissionBuilder(dmaapConfig, dmaapService); + + //when + permissionBuilder.updateDmaapInstance(); + + //then + assertEquals(PermissionBuilder.BOOT_INSTANCE, permissionBuilder.getInstance()); + } + + @Test + public void updateDmaapInstance_shouldSetBootInstance_whenDmaapIsInitializedWithDefaultInstance() { + //given + doReturn(provideDefaultInstance()).when(dmaapService).getDmaap(); + permissionBuilder = new PermissionBuilder(dmaapConfig, dmaapService); + + //when + permissionBuilder.updateDmaapInstance(); + + //then + assertEquals(PermissionBuilder.BOOT_INSTANCE, permissionBuilder.getInstance()); + } + + @Test + public void updateDmaapInstance_shouldSetRealInstance_whenDmaapServiceProvidesOne() { + //given + when(dmaapService.getDmaap()).thenReturn(provideDefaultInstance(), provideRealInstance(DMAAP_NAME)); + permissionBuilder = new PermissionBuilder(dmaapConfig, dmaapService); + + //when + permissionBuilder.updateDmaapInstance(); + + //then + assertEquals(DMAAP_NAME, permissionBuilder.getInstance()); + } + + @Test + public void updateDmaapInstance_shouldNotUpdateDmaapInstance_whenAlreadyInitializedWithRealInstance() { + //given + when(dmaapService.getDmaap()).thenReturn(provideRealInstance(DMAAP_NAME), provideRealInstance("newName")); + permissionBuilder = new PermissionBuilder(dmaapConfig, dmaapService); + + //when + permissionBuilder.updateDmaapInstance(); + + //then + assertEquals(DMAAP_NAME, permissionBuilder.getInstance()); + verify(dmaapService, atMost(1)).getDmaap(); + } + + @Test + public void buildPermission_shouldBuildPermissionWithBootInstance() { + //given + String path = "/dmaap"; + String method = "GET"; + initPermissionBuilder(path, method, provideDefaultInstance()); + + //when + String permission = permissionBuilder.buildPermission(request); + + //then + assertEquals("org.onap.dmaap-bc.api.dmaap|boot|GET", permission); + } + + @Test + public void buildPermission_shouldBuildPermissionWithRealInstance() { + //given + String path = "/subpath/topics/"; + String method = "GET"; + initPermissionBuilder(path, method, provideRealInstance(DMAAP_NAME)); + + //when + String permission = permissionBuilder.buildPermission(request); + + //then + assertEquals("org.onap.dmaap-bc.api.topics|mr|GET", permission); + } + + private void initPermissionBuilder(String path, String method, Dmaap dmaapInstance) { + when(dmaapConfig.getProperty(PermissionBuilder.API_NS_PROP, PermissionBuilder.DEFAULT_API_NS)) + .thenReturn(PermissionBuilder.DEFAULT_API_NS); + when(dmaapService.getDmaap()).thenReturn(dmaapInstance); + permissionBuilder = new PermissionBuilder(dmaapConfig, dmaapService); + + when(request.getPathInfo()).thenReturn(path); + when(request.getMethod()).thenReturn(method); + } + + private Dmaap provideDefaultInstance() { + return new Dmaap("0", "", "", "", "", "", "", ""); + } + + private Dmaap provideRealInstance(String dmaapName) { + Dmaap dmaap = new Dmaap("1", "org.onap.dmaap", dmaapName, "https://dmaap-dr-prov:8443", "", "DCAE_MM_AGENT", "", ""); + dmaap.setStatus(DmaapObject_Status.VALID); + return dmaap; + } + +} \ No newline at end of file -- 2.16.6