Skip to content

feat: add code to create services for SDK #35

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

Merged
merged 64 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
51d0df6
feat: add ability to authenticate with platform services via sdk
strantalis Mar 18, 2024
743568e
bump java version
strantalis Mar 18, 2024
8b12d93
break up mvn steps on check
strantalis Mar 18, 2024
8ffc145
remove gen code
strantalis Mar 18, 2024
94a99d0
revert to java 11
strantalis Mar 18, 2024
bdea8ef
add missing gh token
strantalis Mar 18, 2024
80bcfbb
add gh permissions
strantalis Mar 18, 2024
5bc13ea
add batch-mode
strantalis Mar 18, 2024
9948c62
try setting git config
strantalis Mar 18, 2024
a26e722
try global
strantalis Mar 18, 2024
4e35441
try global
strantalis Mar 18, 2024
704413b
don't set gh token on maven job
strantalis Mar 18, 2024
5738b66
try different user name
strantalis Mar 18, 2024
4517b74
cat gitconfig
strantalis Mar 18, 2024
d227ccb
try something
strantalis Mar 18, 2024
a91b90f
private
strantalis Mar 18, 2024
df1cd08
don't set password
strantalis Mar 18, 2024
35ac02f
fix app name
strantalis Mar 18, 2024
c08a314
try git user again
strantalis Mar 18, 2024
cc929fb
add buf debug flag
strantalis Mar 18, 2024
03a75fe
set buff env vars
strantalis Mar 18, 2024
503417a
try pat
strantalis Mar 18, 2024
e58ee11
fix username
strantalis Mar 18, 2024
8ffb507
try this
ttschampel Mar 18, 2024
501760b
try setting git config
strantalis Mar 18, 2024
f33739a
try again
strantalis Mar 18, 2024
a80b7e2
try buf env vars again
strantalis Mar 18, 2024
8172a65
remove generate token
strantalis Mar 18, 2024
418c50a
add github token back
strantalis Mar 18, 2024
6469c43
try buf env now
strantalis Mar 18, 2024
b81c00a
set username
strantalis Mar 18, 2024
e19e9be
try x-access-token
strantalis Mar 19, 2024
43eb477
try pom setting
strantalis Mar 19, 2024
95e3cea
fix url
strantalis Mar 19, 2024
f93ab80
try a git clone
strantalis Mar 19, 2024
a01fe92
try different env var
strantalis Mar 19, 2024
1f64a48
try withtout cloning
strantalis Mar 19, 2024
ae4df2f
test my pat
strantalis Mar 19, 2024
8db2eb8
try opentdf-bot again
strantalis Mar 19, 2024
4e552eb
switch username
strantalis Mar 19, 2024
4e59aff
switch access token
strantalis Mar 19, 2024
f0942e5
remove dpop for now
strantalis Mar 19, 2024
f3c9eb3
add some comments
strantalis Mar 19, 2024
01ccf61
save
strantalis Mar 21, 2024
82853e0
fix path
mkleene Apr 29, 2024
2b9ef99
refactor
mkleene Apr 29, 2024
73e577e
pull out all of the services
mkleene Apr 29, 2024
ff46119
ok
mkleene Apr 29, 2024
138956b
add a test
mkleene Apr 30, 2024
ce66417
get the test going e2e
mkleene May 1, 2024
9f7a465
Merge remote-tracking branch 'origin/main' into oidc2
mkleene May 1, 2024
495b842
remove this
mkleene May 1, 2024
0ef4a90
don't need those
mkleene May 1, 2024
f95b2d3
didn't need all of that
mkleene May 1, 2024
5a5fc85
ok
mkleene May 1, 2024
a9485e6
Update pom.xml
mkleene May 1, 2024
096f34f
Update pom.xml
mkleene May 1, 2024
ed25a80
add an `SDK` constructor
mkleene May 1, 2024
fdd5082
Merge remote-tracking branch 'origin/oidc2' into oidc2
mkleene May 1, 2024
76ea1b6
name change
mkleene May 1, 2024
cae3973
get the server port in a better way
mkleene May 1, 2024
c33af5b
code review stuff
mkleene May 1, 2024
441eec3
add an exception class
mkleene May 1, 2024
bd8b516
delete this
mkleene May 1, 2024
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
3 changes: 3 additions & 0 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
types:
- checks_requested

permissions:
contents: read

jobs:
pr:
name: Validate PR title
Expand Down
30 changes: 13 additions & 17 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<log4j.version>2.20.0</log4j.version>
<grpc.version>1.57.2</grpc.version>
<grpc.version>1.63.0</grpc.version>
<protobuf.version>3.25.3</protobuf.version>
</properties>
<modules>
Expand All @@ -25,9 +25,18 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentdf.platform</groupId>
<artifactId>platform-client</artifactId>
<version>0.1.0-SNAPSHOT</version><!-- {x-version-update:java-sdk:current} -->
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.10.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
Expand All @@ -50,12 +59,6 @@
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand All @@ -68,13 +71,6 @@
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
Expand Down
4 changes: 1 addition & 3 deletions protocol/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>
<build>
Expand Down
39 changes: 37 additions & 2 deletions sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.opentdf.platform</groupId>
<name>sdk</name>
<artifactId>sdk</artifactId>
<parent>
Expand All @@ -20,7 +19,43 @@
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>11.10.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>5.0.0-alpha.14</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
156 changes: 156 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/GRPCAuthInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package io.opentdf.platform.sdk;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.AuthorizationGrant;
import com.nimbusds.oauth2.sdk.ClientCredentialsGrant;
import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.oauth2.sdk.TokenRequest;
import com.nimbusds.oauth2.sdk.TokenResponse;
import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
import com.nimbusds.oauth2.sdk.dpop.DPoPProofFactory;
import com.nimbusds.oauth2.sdk.dpop.DefaultDPoPProofFactory;
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;

/**
* The GRPCAuthInterceptor class is responsible for intercepting client calls before they are sent
* to the server. It adds authentication headers to the requests by fetching and caching access
* tokens.
*/
class GRPCAuthInterceptor implements ClientInterceptor {
private Instant tokenExpiryTime;
private AccessToken token;
private final ClientAuthentication clientAuth;
private final RSAKey rsaKey;
private final URI tokenEndpointURI;

/**
* Constructs a new GRPCAuthInterceptor with the specified client authentication and RSA key.
*
* @param clientAuth the client authentication to be used by the interceptor
* @param rsaKey the RSA key to be used by the interceptor
*/
public GRPCAuthInterceptor(ClientAuthentication clientAuth, RSAKey rsaKey, URI tokenEndpointURI) {
this.clientAuth = clientAuth;
this.rsaKey = rsaKey;
this.tokenEndpointURI = tokenEndpointURI;
}

/**
* Intercepts the client call before it is sent to the server.
*
* @param method The method descriptor for the call.
* @param callOptions The call options for the call.
* @param next The next channel in the channel pipeline.
* @param <ReqT> The type of the request message.
* @param <RespT> The type of the response message.
* @return A client call with the intercepted behavior.
*/
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions, Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall<>(next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
// Get the access token
AccessToken t = getToken();
headers.put(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER),
"DPoP " + t.getValue());

// Build the DPoP proof for each request
try {
DPoPProofFactory dpopFactory = new DefaultDPoPProofFactory(rsaKey, JWSAlgorithm.RS256);

URI uri = new URI("/" + method.getFullMethodName());
SignedJWT proof = dpopFactory.createDPoPJWT("POST", uri, t);
headers.put(Metadata.Key.of("DPoP", Metadata.ASCII_STRING_MARSHALLER),
proof.serialize());
} catch (URISyntaxException e) {
throw new RuntimeException("Invalid URI syntax for DPoP proof creation", e);
} catch (JOSEException e) {
throw new RuntimeException("Error creating DPoP proof", e);
}
super.start(responseListener, headers);
}
};
}

/**
* Either fetches a new access token or returns the cached access token if it is still valid.
*
* @return The access token.
*/
private synchronized AccessToken getToken() {
try {
// If the token is expired or initially null, get a new token
if (token == null || isTokenExpired()) {

// Construct the client credentials grant
AuthorizationGrant clientGrant = new ClientCredentialsGrant();

// Make the token request
TokenRequest tokenRequest = new TokenRequest(this.tokenEndpointURI,
clientAuth, clientGrant, null);
HTTPRequest httpRequest = tokenRequest.toHTTPRequest();

DPoPProofFactory dpopFactory = new DefaultDPoPProofFactory(rsaKey, JWSAlgorithm.RS256);

SignedJWT proof = dpopFactory.createDPoPJWT(httpRequest.getMethod().name(), httpRequest.getURI());

httpRequest.setDPoP(proof);
TokenResponse tokenResponse;

HTTPResponse httpResponse = httpRequest.send();

tokenResponse = TokenResponse.parse(httpResponse);
if (!tokenResponse.indicatesSuccess()) {
ErrorObject error = tokenResponse.toErrorResponse().getErrorObject();
throw new RuntimeException("Token request failed: " + error);
}

this.token = tokenResponse.toSuccessResponse().getTokens().getAccessToken();
// DPoPAccessToken dPoPAccessToken = tokens.getDPoPAccessToken();


if (token.getLifetime() != 0) {
// Need some type of leeway but not sure whats best
this.tokenExpiryTime = Instant.now().plusSeconds(token.getLifetime() / 3);
}

} else {
// If the token is still valid or not initially null, return the cached token
return this.token;
}

} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException("failed to get token", e);
}
return this.token;
}

/**
* Checks if the token has expired.
*
* @return true if the token has expired, false otherwise.
*/
private boolean isTokenExpired() {
return this.tokenExpiryTime != null && this.tokenExpiryTime.isBefore(Instant.now());
}
}
93 changes: 49 additions & 44 deletions sdk/src/main/java/io/opentdf/platform/sdk/SDK.java
Original file line number Diff line number Diff line change
@@ -1,55 +1,60 @@
package io.opentdf.platform.sdk;

import io.grpc.Channel;
import io.opentdf.platform.policy.attributes.AttributesServiceGrpc;
import io.opentdf.platform.policy.attributes.AttributesServiceGrpc.AttributesServiceFutureStub;
import io.opentdf.platform.policy.namespaces.NamespaceServiceGrpc;
import io.opentdf.platform.policy.namespaces.NamespaceServiceGrpc.NamespaceServiceFutureStub;
import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceGrpc;
import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceGrpc.ResourceMappingServiceFutureStub;
import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceGrpc;
import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceGrpc.SubjectMappingServiceFutureStub;

/**
* Interact with OpenTDF platform services and perform TDF data operations with
* this object.
* The SDK class represents a software development kit for interacting with the opentdf platform. It
* provides various services and stubs for making API calls to the opentdf platform.
*/
public class SDK {
private final String platformEndpoint;
private AttributesServiceGrpc.AttributesServiceFutureStub attributesServiceFutureStub;
private ResourceMappingServiceGrpc.ResourceMappingServiceFutureStub resourceMappingServiceFutureStub;

public AttributesServiceGrpc.AttributesServiceFutureStub getAttributesServiceFutureStub() {
return attributesServiceFutureStub;
}

public ResourceMappingServiceGrpc.ResourceMappingServiceFutureStub getResourceMappingServiceFutureStub() {
return resourceMappingServiceFutureStub;
}


private SDK(String platformEndpoint) {
this.platformEndpoint = platformEndpoint;
}

public String getPlatformEndpoint() {
return this.platformEndpoint;
}

/**
* Builder pattern for SDK objects
*/
public static class Builder implements Cloneable {
private String platformEndpoint;

public Builder platformEndpoint(String platformEndpoint) {
this.platformEndpoint = platformEndpoint;
return this;
}

public SDK build() {
return new SDK(this.platformEndpoint);
private final Services services;

// TODO: add KAS
public interface Services {
AttributesServiceFutureStub attributes();
NamespaceServiceFutureStub namespaces();
SubjectMappingServiceFutureStub subjectMappings();
ResourceMappingServiceFutureStub resourceMappings();

static Services newServices(Channel channel) {
var attributeService = AttributesServiceGrpc.newFutureStub(channel);
var namespaceService = NamespaceServiceGrpc.newFutureStub(channel);
var subjectMappingService = SubjectMappingServiceGrpc.newFutureStub(channel);
var resourceMappingService = ResourceMappingServiceGrpc.newFutureStub(channel);

return new Services() {
@Override
public AttributesServiceFutureStub attributes() {
return attributeService;
}

@Override
public NamespaceServiceFutureStub namespaces() {
return namespaceService;
}

@Override
public SubjectMappingServiceFutureStub subjectMappings() {
return subjectMappingService;
}

@Override
public ResourceMappingServiceFutureStub resourceMappings() {
return resourceMappingService;
}
};
}
}

@Override public Builder clone() {
try {
return (Builder) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
public SDK(Services services) {
this.services = services;
}
}
}
}
Loading