diff --git a/build.gradle b/build.gradle index 0e0cc644403..45856e0d546 100644 --- a/build.gradle +++ b/build.gradle @@ -94,6 +94,9 @@ allprojects { junitjupiterVersion = '5.9.1' junitplatformVersion = '1.9.1' jwtVersion = '0.9.1' + jwtApiVersion = '0.11.5' + jwtImplVersion = '0.11.5' + jwtJacksonVersion = '0.11.5' kafkaVersion = '3.3.1' lang3Version = '3.12.0' logbackVersion = '1.2.11' @@ -162,6 +165,7 @@ allprojects { implementation("org.apache.commons:commons-text:${commonstextVersion}") implementation("io.github.classgraph:classgraph:${classgraphVersion}") implementation("io.jsonwebtoken:jjwt:${jwtVersion}") + implementation("io.jsonwebtoken:jjwt-api:${jwtApiVersion}") implementation("io.lettuce:lettuce-core:${lettuceVersion}") implementation("io.micrometer:micrometer-registry-prometheus:${prometheusVersion}") implementation("io.netty:netty-handler:${nettyVersion}") @@ -280,6 +284,8 @@ allprojects { runtimeOnly("org.janusgraph:janusgraph-es:${janusVersion}") runtimeOnly("org.xerial.snappy:snappy-java:${snappyVersion}") runtimeOnly("javax.servlet:javax.servlet-api:${servletVersion}") + runtimeOnly("io.jsonwebtoken:jjwt-impl:${jwtImplVersion}") + runtimeOnly("io.jsonwebtoken:jjwt-jackson:${jwtJacksonVersion}") testImplementation("junit:junit:${junitVersion}") testImplementation("org.glassfish:javax.json:${glassfishVersion}") testImplementation("org.junit.jupiter:junit-jupiter:${junitjupiterVersion}") diff --git a/open-metadata-implementation/adapters/authentication-plugins/http-helper/src/main/java/org/odpi/openmetadata/http/HttpRequestHeadersFilter.java b/open-metadata-implementation/adapters/authentication-plugins/http-helper/src/main/java/org/odpi/openmetadata/http/HttpRequestHeadersFilter.java index 812bb57eb6d..7c2c983071b 100644 --- a/open-metadata-implementation/adapters/authentication-plugins/http-helper/src/main/java/org/odpi/openmetadata/http/HttpRequestHeadersFilter.java +++ b/open-metadata-implementation/adapters/authentication-plugins/http-helper/src/main/java/org/odpi/openmetadata/http/HttpRequestHeadersFilter.java @@ -42,7 +42,10 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo for (String headerName : headerNames) { String headerValue = req.getHeader(headerName); - threadLocalHeaders.put(headerName, headerValue); + + if (headerValue != null && !headerValue.isEmpty()) { + threadLocalHeaders.put(headerName, headerValue); + } } HttpHeadersThreadLocal.getHeadersThreadLocal().set(threadLocalHeaders); diff --git a/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/build.gradle b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/build.gradle index b4618c5f789..02fb5981cec 100644 --- a/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/build.gradle +++ b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/build.gradle @@ -5,12 +5,17 @@ dependencies { + implementation 'io.jsonwebtoken:jjwt-api' + implementation 'org.slf4j:slf4j-api' implementation project(':open-metadata-implementation:common-services:metadata-security:metadata-security-connectors') implementation project(':open-metadata-implementation:repository-services:repository-services-apis') implementation project(':open-metadata-implementation:frameworks:open-connector-framework') implementation project(':open-metadata-implementation:frameworks:audit-log-framework') implementation project(':open-metadata-implementation:common-services:metadata-security:metadata-security-apis') + implementation project(':open-metadata-implementation:adapters:authentication-plugins:http-helper') compileOnly 'com.fasterxml.jackson.core:jackson-annotations' + runtimeOnly 'io.jsonwebtoken:jjwt-impl' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson' } diff --git a/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/pom.xml b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/pom.xml index 371ad045d08..3d33d58b087 100644 --- a/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/pom.xml +++ b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/pom.xml @@ -39,6 +39,11 @@ repository-services-apis + + org.odpi.egeria + http-helper + + org.odpi.egeria open-connector-framework @@ -54,6 +59,74 @@ metadata-security-apis + + io.jsonwebtoken + jjwt-api + + + + io.jsonwebtoken + jjwt-impl + + + + io.jsonwebtoken + jjwt-jackson + + + + org.slf4j + slf4j-api + + + + + maven-assembly-plugin + + + package + + single + + + + + + org.odpi.openmetadata.metadatasecurity.samples.CocoPharmaServerSecurityConnectorTokenBased + + + + + jar-with-dependencies + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + analyze + + analyze-only + + + + + io.jsonwebtoken:jjwt-impl:* + + io.jsonwebtoken:jjwt-jackson:* + + + + + + + + + diff --git a/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/src/main/java/org/odpi/openmetadata/metadatasecurity/samples/CocoPharmaPlatformSecurityConnectorTokenBased.java b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/src/main/java/org/odpi/openmetadata/metadatasecurity/samples/CocoPharmaPlatformSecurityConnectorTokenBased.java new file mode 100644 index 00000000000..acd25192180 --- /dev/null +++ b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/src/main/java/org/odpi/openmetadata/metadatasecurity/samples/CocoPharmaPlatformSecurityConnectorTokenBased.java @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: Apache 2.0 */ +/* Copyright Contributors to the ODPi Egeria project. */ +package org.odpi.openmetadata.metadatasecurity.samples; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.odpi.openmetadata.frameworks.connectors.ffdc.UserNotAuthorizedException; +import org.odpi.openmetadata.http.HttpHeadersThreadLocal; +import org.odpi.openmetadata.metadatasecurity.connectors.OpenMetadataPlatformSecurityConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Base64; +import java.util.List; +import java.util.Map; + +/** + * CocoPharmaPlatformSecurityConnector overrides the default behavior for the security connector + * to allow requests the Coco Pharmaceutical's server administrator APIs. In this example, + * only Gary Geeke is allowed to issue these requests. + * + * To generate a JWT for this example, we used the following payload: + * { + * "sub": "garygeeke", + * "name": "Gary Geeke", + * "actions":["platform-administrator","platform-operator","platform-investigator"], + * "iat": {Epoch timestamp}, + * "exp": {Epoch timestamp} + * } + */ +public class CocoPharmaPlatformSecurityConnectorTokenBased extends OpenMetadataPlatformSecurityConnector { + private enum PlatformRoles { + PLATFORM_ADMINISTRATOR, + PLATFORM_OPERATOR, + PLATFORM_INVESTIGATOR; + + private String getName() { + return this.toString().toLowerCase(); + } + } + + //secret used to decrypt the given token + private final byte[] secret = Base64.getDecoder().decode("d14uaEwsGU3cXopmxaEDqhQTow81zixFWbFUuu3budQ"); + + private static final Logger log = LoggerFactory.getLogger(CocoPharmaPlatformSecurityConnectorTokenBased.class); + + + /** + * Check that the calling user is authorized to create new servers. + * + * @param userId calling user + * @throws UserNotAuthorizedException the user is not authorized to access this platform + */ + @Override + public void validateUserForNewServer(String userId) throws UserNotAuthorizedException { + final String methodName = "validateUserForNewServer"; + + if (!isAllowedToPerformAction(userId, PlatformRoles.PLATFORM_ADMINISTRATOR)) { + super.throwUnauthorizedPlatformAccess(userId, methodName); + } + } + + + /** + * Check that the calling user is authorized to issue operator requests to the OMAG Server Platform. + * + * @param userId calling user + * @throws UserNotAuthorizedException the user is not authorized to issue operator commands to this platform + */ + @Override + public void validateUserAsOperatorForPlatform(String userId) throws UserNotAuthorizedException { + final String methodName = "validateUserAsOperatorForPlatform"; + + if (!isAllowedToPerformAction(userId, PlatformRoles.PLATFORM_OPERATOR)) { + super.throwUnauthorizedPlatformAccess(userId, methodName); + } + } + + + /** + * Check that the calling user is authorized to issue operator requests to the OMAG Server Platform. + * + * @param userId calling user + * @throws UserNotAuthorizedException the user is not authorized to issue diagnostic commands to this platform + */ + @Override + public void validateUserAsInvestigatorForPlatform(String userId) throws UserNotAuthorizedException { + final String methodName = "validateUserAsInvestigatorForPlatform"; + + if (!isAllowedToPerformAction(userId, PlatformRoles.PLATFORM_INVESTIGATOR)) { + super.throwUnauthorizedPlatformAccess(userId, methodName); + } + } + + private List getUserActionsFromToken(String userId) { + Map headersMap = HttpHeadersThreadLocal.getHeadersThreadLocal().get(); + if (headersMap != null && !headersMap.isEmpty()) { + Jws jwtClaims = Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(secret)) + .build().parseClaimsJws(headersMap.get("authorization")); + + String username = jwtClaims.getBody().getSubject(); + List actions = jwtClaims.getBody().get("actions", List.class); + if (username.equals(userId) && actions != null && !actions.isEmpty()) { + log.info("User {} validated for issuing requests.", username); + return actions; + } + } + return null; + } + + private Boolean isAllowedToPerformAction(String userId, PlatformRoles role) { + List userActions = getUserActionsFromToken(userId); + + if (userActions != null && !userActions.isEmpty() && userActions.contains(role.getName())) { + return true; + } + return false; + } +} diff --git a/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/src/main/java/org/odpi/openmetadata/metadatasecurity/samples/CocoPharmaPlatformSecurityProviderTokenBased.java b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/src/main/java/org/odpi/openmetadata/metadatasecurity/samples/CocoPharmaPlatformSecurityProviderTokenBased.java new file mode 100644 index 00000000000..ceeda68b186 --- /dev/null +++ b/open-metadata-resources/open-metadata-samples/open-metadata-security-samples/src/main/java/org/odpi/openmetadata/metadatasecurity/samples/CocoPharmaPlatformSecurityProviderTokenBased.java @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: Apache 2.0 */ +/* Copyright Contributors to the ODPi Egeria project. */ +package org.odpi.openmetadata.metadatasecurity.samples; + +import org.odpi.openmetadata.frameworks.auditlog.AuditLogReportingComponent; +import org.odpi.openmetadata.frameworks.connectors.properties.beans.ConnectorType; +import org.odpi.openmetadata.metadatasecurity.connectors.OpenMetadataPlatformSecurityProvider; + +/** + * CocoPharmaPlatformSecurityProviderTokenBased is the connector provider to the + * sample platform security connector for the Coco Pharmaceuticals scenarios. + */ +public class CocoPharmaPlatformSecurityProviderTokenBased extends OpenMetadataPlatformSecurityProvider +{ + /* + * Unique identifier of the connector for the audit log. + */ + private static final int connectorComponentId = 93; + + /* + * Unique identifier for the connector type. + */ + private static final String connectorTypeGUID = "5e6f852f-5912-44fb-aa6c-784efe25af47"; + + /* + * Descriptive information about the connector for the connector type and audit log. + */ + private static final String connectorQualifiedName = "Egeria:Sample:TokenBased:PlatformSecurity:CocoPharmaceuticals"; + private static final String connectorDisplayName = "Coco Pharmaceuticals Platform Security Connector Token Based"; + private static final String connectorDescription = "Connector that exposes the usability of custom authorisation headers for Coco Pharmaceuticals."; + + /* + * Class of the connector. + */ + private static final Class connectorClass = CocoPharmaPlatformSecurityConnectorTokenBased.class; + + + /** + * Constructor used to initialize the ConnectorProviderBase with the Java class name of the specific + * registry store implementation. + */ + public CocoPharmaPlatformSecurityProviderTokenBased() + { + super(); + + /* + * Set up the class name of the connector that this provider creates. + */ + super.setConnectorClassName(connectorClass.getName()); + + /* + * Set up the connector type that should be included in a connection used to configure this connector. + */ + ConnectorType connectorType = new ConnectorType(); + connectorType.setType(ConnectorType.getConnectorTypeType()); + connectorType.setGUID(connectorTypeGUID); + connectorType.setQualifiedName(connectorQualifiedName); + connectorType.setDisplayName(connectorDisplayName); + connectorType.setDescription(connectorDescription); + connectorType.setConnectorProviderClassName(this.getClass().getName()); + + super.connectorTypeBean = connectorType; + + /* + * Set up the component description used in the connector's audit log messages. + */ + AuditLogReportingComponent componentDescription = new AuditLogReportingComponent(); + + componentDescription.setComponentId(connectorComponentId); + componentDescription.setComponentName(connectorQualifiedName); + componentDescription.setComponentDescription(connectorDescription); + + super.setConnectorComponentDescription(componentDescription); + } + +} diff --git a/pom.xml b/pom.xml index 9c6b23ea4d1..c20b3a29ec5 100644 --- a/pom.xml +++ b/pom.xml @@ -177,6 +177,9 @@ 4.2.0 3.12.0 0.9.1 + 0.11.5 + 0.11.5 + 0.11.5 3.1.0 3.0.2 2.0.1.Final @@ -1937,6 +1940,27 @@ ${open-metadata.version} + + io.jsonwebtoken + jjwt-api + compile + ${jjwt-api.version} + + + + io.jsonwebtoken + jjwt-impl + runtime + ${jjwt-impl.version} + + + + io.jsonwebtoken + jjwt-jackson + runtime + ${jjwt-jackson.version} + + org.odpi.egeria metadata-security-connectors