Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 4 additions & 1 deletion .gitignore
Comment thread
fjtirado marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ build/
.vscode/

# Bob-Shell
.bob/notes/
.bob/notes/


impl/test/*/**.db
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@

import static io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient.ClientAuthentication.CLIENT_SECRET_POST;
import static io.serverlessworkflow.impl.WorkflowUtils.isValid;
import static io.serverlessworkflow.impl.auth.AuthUtils.ACTOR;
import static io.serverlessworkflow.impl.auth.AuthUtils.ACTOR_TOKEN;
import static io.serverlessworkflow.impl.auth.AuthUtils.ACTOR_TOKEN_TYPE;
import static io.serverlessworkflow.impl.auth.AuthUtils.AUDIENCES;
import static io.serverlessworkflow.impl.auth.AuthUtils.AUTHENTICATION;
import static io.serverlessworkflow.impl.auth.AuthUtils.CLIENT;
import static io.serverlessworkflow.impl.auth.AuthUtils.ENCODING;
import static io.serverlessworkflow.impl.auth.AuthUtils.REQUEST;
import static io.serverlessworkflow.impl.auth.AuthUtils.SCOPES;
import static io.serverlessworkflow.impl.auth.AuthUtils.SUBJECT;
import static io.serverlessworkflow.impl.auth.AuthUtils.SUBJECT_TOKEN;
import static io.serverlessworkflow.impl.auth.AuthUtils.SUBJECT_TOKEN_TYPE;
import static io.serverlessworkflow.impl.auth.AuthUtils.TOKEN;
import static io.serverlessworkflow.impl.auth.AuthUtils.TYPE;

import io.serverlessworkflow.api.types.OAuth2AuthenticationData;
import io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient;
import io.serverlessworkflow.api.types.OAuth2TokenDefinition;
import io.serverlessworkflow.impl.WorkflowApplication;
import io.serverlessworkflow.impl.WorkflowUtils;
import java.util.Arrays;
Expand All @@ -51,6 +60,7 @@ public HttpRequestInfo apply(T authenticationData) {
audience(authenticationData);
scope(authenticationData);
authenticationMethod(authenticationData);
subjectActor(authenticationData);
return requestBuilder.build();
}

Expand All @@ -61,6 +71,7 @@ public HttpRequestInfo apply(Map<String, Object> secret) {
audience(secret);
scope(secret);
authenticationMethod(secret);
subjectActor(secret);
return requestBuilder.build();
}

Expand All @@ -80,44 +91,62 @@ protected void audience(Map<String, Object> secret) {
}

protected void authenticationMethod(T authenticationData) {
ClientSecretHandler secretHandler;
switch (getClientAuthentication(authenticationData)) {
case CLIENT_SECRET_BASIC:
secretHandler = new ClientSecretBasic(application, requestBuilder);
case CLIENT_SECRET_JWT:
throw new UnsupportedOperationException("Client Secret JWT is not supported yet");
case PRIVATE_KEY_JWT:
throw new UnsupportedOperationException("Private Key JWT is not supported yet");
default:
secretHandler = new ClientSecretPost(application, requestBuilder);
}
ClientSecretHandler secretHandler =
switch (getClientAuthentication(authenticationData)) {
case CLIENT_SECRET_BASIC -> new ClientSecretBasic(application, requestBuilder);
case CLIENT_SECRET_JWT, PRIVATE_KEY_JWT ->
new JwtClientAssertion(application, requestBuilder);
default -> new ClientSecretPost(application, requestBuilder);
};
secretHandler.accept(authenticationData);
}

@SuppressWarnings("unchecked")
protected void authenticationMethod(Map<String, Object> secret) {
Map<String, Object> client = (Map<String, Object>) secret.get(CLIENT);
ClientSecretHandler secretHandler;
Comment thread
fjtirado marked this conversation as resolved.
String auth = (String) client.get(AUTHENTICATION);
if (auth == null) {
secretHandler = new ClientSecretPost(application, requestBuilder);
} else {
switch (auth) {
case "client_secret_basic":
secretHandler = new ClientSecretBasic(application, requestBuilder);
break;
default:
case "client_secret_post":
secretHandler = new ClientSecretPost(application, requestBuilder);
break;
case "private_key_jwt":
throw new UnsupportedOperationException("Private Key JWT is not supported yet");
case "client_secret_jwt":
throw new UnsupportedOperationException("Client Secret JWT is not supported yet");
}
secretHandler =
switch (auth) {
case "client_secret_basic" -> new ClientSecretBasic(application, requestBuilder);
case "private_key_jwt", "client_secret_jwt" ->
new JwtClientAssertion(application, requestBuilder);
default -> new ClientSecretPost(application, requestBuilder);
};
}
secretHandler.accept(secret);
}

protected void subjectActor(T authenticationData) {
tokenParam(SUBJECT_TOKEN, SUBJECT_TOKEN_TYPE, authenticationData.getSubject());
tokenParam(ACTOR_TOKEN, ACTOR_TOKEN_TYPE, authenticationData.getActor());
}

private void tokenParam(String tokenKey, String typeKey, OAuth2TokenDefinition definition) {
if (definition != null) {
requestBuilder
.addQueryParam(
tokenKey, WorkflowUtils.buildStringFilter(application, definition.getToken()))
.addQueryParam(typeKey, definition.getType());
}
}
Comment thread
fjtirado marked this conversation as resolved.

protected void subjectActor(Map<String, Object> secret) {
tokenParam(SUBJECT_TOKEN, SUBJECT_TOKEN_TYPE, secret.get(SUBJECT));
tokenParam(ACTOR_TOKEN, ACTOR_TOKEN_TYPE, secret.get(ACTOR));
}

private void tokenParam(String tokenKey, String typeKey, Object rawDefinition) {
if (rawDefinition instanceof Map<?, ?> definition) {
requestBuilder
.addQueryParam(tokenKey, (String) definition.get(TOKEN))
.addQueryParam(typeKey, (String) definition.get(TYPE));
}
}
Comment thread
fjtirado marked this conversation as resolved.

private OAuth2AuthenticationDataClient.ClientAuthentication getClientAuthentication(
OAuth2AuthenticationData authenticationData) {
return authenticationData.getClient() == null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,45 @@

public interface AccessTokenProvider {
JWT validateAndGet(WorkflowContext workflow, TaskContext context, WorkflowModel model);

/**
* Introspects the given token against the configured introspection endpoint, as defined by <a
* href="https://www.rfc-editor.org/rfc/rfc7662">RFC 7662</a>.
*
* <p>This is an optional capability. The default implementation throws {@link
* UnsupportedOperationException}; providers backed by an introspection-capable OIDC client should
* override it.
*
* @param tokenTypeHint optional {@code token_type_hint} (e.g. {@code access_token}), may be
* {@code null}
*/
default TokenIntrospection introspect(
WorkflowContext workflow,
TaskContext context,
WorkflowModel model,
String token,
String tokenTypeHint) {
throw new UnsupportedOperationException(
"Token introspection is not supported by this provider");
Comment thread
fjtirado marked this conversation as resolved.
Outdated
}

/**
* Revokes the given token against the configured revocation endpoint, as defined by <a
* href="https://www.rfc-editor.org/rfc/rfc7009">RFC 7009</a>.
*
* <p>This is an optional capability. The default implementation throws {@link
* UnsupportedOperationException}; providers backed by a revocation-capable OIDC client should
* override it.
*
* @param tokenTypeHint optional {@code token_type_hint} (e.g. {@code access_token}, {@code
* refresh_token}), may be {@code null}
*/
default void revoke(
WorkflowContext workflow,
TaskContext context,
WorkflowModel model,
String token,
String tokenTypeHint) {
throw new UnsupportedOperationException("Token revocation is not supported by this provider");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ private AuthUtils() {}
public static final String REQUEST = "request";
public static final String ENCODING = "encoding";
public static final String AUTHENTICATION = "authentication";
public static final String ASSERTION = "assertion";
public static final String SUBJECT = "subject";
public static final String ACTOR = "actor";
public static final String TYPE = "type";
public static final String REVOCATION = "revocation";
public static final String INTROSPECTION = "introspection";

public static final String CLIENT_ID = "client_id";
public static final String CLIENT_ASSERTION = "client_assertion";
public static final String CLIENT_ASSERTION_TYPE = "client_assertion_type";
public static final String JWT_BEARER_ASSERTION_TYPE =
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer";

public static final String SUBJECT_TOKEN = "subject_token";
public static final String SUBJECT_TOKEN_TYPE = "subject_token_type";
public static final String ACTOR_TOKEN = "actor_token";
public static final String ACTOR_TOKEN_TYPE = "actor_token_type";

private static final String AUTH_HEADER_FORMAT = "%s %s";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS;
import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.PASSWORD;
import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE;

import io.serverlessworkflow.api.types.OAuth2AuthenticationData;
import io.serverlessworkflow.impl.WorkflowApplication;
Expand Down Expand Up @@ -48,7 +49,8 @@ void accept(OAuth2AuthenticationData authenticationData) {
}

password(authenticationData);
} else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)) {
} else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)
|| authenticationData.getGrant().equals(URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE)) {
if (authenticationData.getClient() == null
|| authenticationData.getClient().getId() == null
|| authenticationData.getClient().getSecret() == null) {
Expand All @@ -74,6 +76,7 @@ void accept(Map<String, Object> secret) {
String grant = Objects.requireNonNull((String) secret.get("grant"), "Grant is mandatory field");
switch (grant) {
case "client_credentials":
case "urn:ietf:params:oauth:grant-type:token-exchange":
clientCredentials(secret);
break;
case "password":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ protected ClientSecretPost(
protected void clientCredentials(OAuth2AuthenticationData authenticationData) {
requestBuilder
.withGrantType(authenticationData.getGrant().value())
.addQueryParam(
.addClientAuthParam(
"client_id",
WorkflowUtils.buildStringFilter(application, authenticationData.getClient().getId()))
.addQueryParam(
.addClientAuthParam(
"client_secret",
WorkflowUtils.buildStringFilter(
application, authenticationData.getClient().getSecret()));
Expand All @@ -64,8 +64,8 @@ protected void clientCredentials(Map<String, Object> secret) {
Map<String, Object> client = (Map<String, Object>) secret.get(CLIENT);
requestBuilder
.withGrantType((String) secret.get(GRANT))
.addQueryParam("client_id", (String) client.get(ID))
.addQueryParam("client_secret", (String) client.get(SECRET));
.addClientAuthParam("client_id", (String) client.get(ID))
.addClientAuthParam("client_secret", (String) client.get(SECRET));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
import io.serverlessworkflow.impl.WorkflowValueResolver;
import java.net.URI;
import java.util.Map;
import java.util.Optional;

public record HttpRequestInfo(
Map<String, WorkflowValueResolver<String>> headers,
Map<String, WorkflowValueResolver<String>> queryParams,
Map<String, WorkflowValueResolver<String>> clientAuthParams,
WorkflowValueResolver<URI> uri,
Optional<WorkflowValueResolver<URI>> revocationUri,
Optional<WorkflowValueResolver<URI>> introspectionUri,
String grantType,
String contentType) {}
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,30 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

class HttpRequestInfoBuilder {

private Map<String, WorkflowValueResolver<String>> headers;

private Map<String, WorkflowValueResolver<String>> queryParams;

private Map<String, WorkflowValueResolver<String>> clientAuthParams;

private WorkflowValueResolver<URI> uri;

private Optional<WorkflowValueResolver<URI>> revocationUri = Optional.empty();

private Optional<WorkflowValueResolver<URI>> introspectionUri = Optional.empty();

private String grantType;

private String contentType;

HttpRequestInfoBuilder() {
headers = new HashMap<>();
queryParams = new HashMap<>();
clientAuthParams = new HashMap<>();
}

HttpRequestInfoBuilder addHeader(String key, String token) {
Expand All @@ -61,11 +69,32 @@ HttpRequestInfoBuilder addQueryParam(String key, WorkflowValueResolver<String> t
return this;
}

HttpRequestInfoBuilder addClientAuthParam(String key, String token) {
clientAuthParams.put(key, (w, t, m) -> token);
return this;
}

HttpRequestInfoBuilder addClientAuthParam(String key, WorkflowValueResolver<String> token) {
clientAuthParams.put(key, token);
return this;
}

HttpRequestInfoBuilder withUri(WorkflowValueResolver<URI> uri) {
this.uri = uri;
return this;
}

HttpRequestInfoBuilder withRevocationUri(Optional<WorkflowValueResolver<URI>> revocationUri) {
this.revocationUri = revocationUri;
return this;
}

HttpRequestInfoBuilder withIntrospectionUri(
Optional<WorkflowValueResolver<URI>> introspectionUri) {
this.introspectionUri = introspectionUri;
return this;
}

HttpRequestInfoBuilder withContentType(OAuth2TokenRequest oAuth2TokenRequest) {
if (oAuth2TokenRequest != null) {
this.contentType = oAuth2TokenRequest.getEncoding().value();
Expand All @@ -91,6 +120,14 @@ HttpRequestInfo build() {
if (contentType == null) {
contentType = APPLICATION_X_WWW_FORM_URLENCODED.value();
}
return new HttpRequestInfo(headers, queryParams, uri, grantType, contentType);
return new HttpRequestInfo(
headers,
queryParams,
clientAuthParams,
uri,
revocationUri,
introspectionUri,
grantType,
contentType);
}
}
Loading
Loading