Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate HTTP headers - Coco Pharma example #6977

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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}")
Expand Down Expand Up @@ -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}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
<artifactId>repository-services-apis</artifactId>
</dependency>

<dependency>
<groupId>org.odpi.egeria</groupId>
<artifactId>http-helper</artifactId>
</dependency>

<dependency>
<groupId>org.odpi.egeria</groupId>
<artifactId>open-connector-framework</artifactId>
Expand All @@ -54,6 +59,74 @@
<artifactId>metadata-security-apis</artifactId>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>
org.odpi.openmetadata.metadatasecurity.samples.CocoPharmaServerSecurityConnectorTokenBased
</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze-only</goal>
</goals>
<configuration>
<ignoredUnusedDeclaredDependencies combine.children="append">
<!-- Runtime dependencies for Coco Pharma token based connector-->
<ignoredUnusedDeclaredDependency>io.jsonwebtoken:jjwt-impl:*
</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>io.jsonwebtoken:jjwt-jackson:*
</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* 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.
*/
MihaiIliescu marked this conversation as resolved.
Show resolved Hide resolved
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
*/
public void validateUserForNewServer(String userId) throws UserNotAuthorizedException {
Fixed Show fixed Hide fixed
final String methodName = "validateUserForNewServer";
MihaiIliescu marked this conversation as resolved.
Show resolved Hide resolved

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
*/
public void validateUserAsOperatorForPlatform(String userId) throws UserNotAuthorizedException {
Fixed Show fixed Hide fixed
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
*/
public void validateUserAsInvestigatorForPlatform(String userId) throws UserNotAuthorizedException {
Fixed Show fixed Hide fixed
final String methodName = "validateUserAsInvestigatorForPlatform";
MihaiIliescu marked this conversation as resolved.
Show resolved Hide resolved

if (!isAllowedToPerformAction(userId, PlatformRoles.PLATFORM_INVESTIGATOR)) {
super.throwUnauthorizedPlatformAccess(userId, methodName);
}
}

private List<String> getUserActionsFromToken(String userId) {
Map<String, String> headersMap = HttpHeadersThreadLocal.getHeadersThreadLocal().get();
if (headersMap != null && !headersMap.isEmpty()) {
Jws<Claims> jwtClaims = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secret))
.build().parseClaimsJws(headersMap.get("authorization"));

String username = jwtClaims.getBody().getSubject();
List<String> 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<String> userActions = getUserActionsFromToken(userId);

if (userActions != null && !userActions.isEmpty() && userActions.contains(role.getName())) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -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();
MihaiIliescu marked this conversation as resolved.
Show resolved Hide resolved
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();
MihaiIliescu marked this conversation as resolved.
Show resolved Hide resolved

componentDescription.setComponentId(connectorComponentId);
componentDescription.setComponentName(connectorQualifiedName);
componentDescription.setComponentDescription(connectorDescription);

super.setConnectorComponentDescription(componentDescription);
}

}
24 changes: 24 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
<jena.version>4.2.0</jena.version>
<commons-lang.version>3.12.0</commons-lang.version>
<jwt.version>0.9.1</jwt.version>
<jjwt-api.version>0.11.5</jjwt-api.version>
<jjwt-impl.version>0.11.5</jjwt-impl.version>
<jjwt-jackson.version>0.11.5</jjwt-jackson.version>
<jakarta.persistence.version>3.1.0</jakarta.persistence.version>
<jakarta-validation.version>3.0.2</jakarta-validation.version>
<javax-validation.version>2.0.1.Final</javax-validation.version>
Expand Down Expand Up @@ -1937,6 +1940,27 @@
<version>${open-metadata.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<scope>compile</scope>
<version>${jjwt-api.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
<version>${jjwt-impl.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
<version>${jjwt-jackson.version}</version>
</dependency>

<dependency>
<groupId>org.odpi.egeria</groupId>
<artifactId>metadata-security-connectors</artifactId>
Expand Down