Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import io.serverlessworkflow.impl.TaskContext;
import io.serverlessworkflow.impl.WorkflowContext;
import io.serverlessworkflow.impl.WorkflowModel;
import java.util.concurrent.CompletableFuture;

public interface AccessTokenProvider {
JWT validateAndGet(WorkflowContext workflow, TaskContext context, WorkflowModel model);
CompletableFuture<JWT> validateAndGet(
WorkflowContext workflow, TaskContext context, WorkflowModel model);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import io.serverlessworkflow.impl.WorkflowContext;
import io.serverlessworkflow.impl.WorkflowModel;
import java.net.URI;
import java.util.concurrent.CompletableFuture;

public interface AuthProvider {

String scheme();

String content(WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri);
CompletableFuture<String> content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.serverlessworkflow.impl.WorkflowValueResolver;
import java.net.URI;
import java.util.Base64;
import java.util.concurrent.CompletableFuture;

class BasicAuthProvider implements AuthProvider {

Expand Down Expand Up @@ -58,15 +59,17 @@ public BasicAuthProvider(
}

@Override
public String content(WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
return new String(
Base64.getEncoder()
.encode(
String.format(
USER_PASSWORD,
userFilter.apply(workflow, task, model),
passwordFilter.apply(workflow, task, model))
.getBytes()));
public CompletableFuture<String> content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
return CompletableFuture.completedFuture(
new String(
Base64.getEncoder()
.encode(
String.format(
USER_PASSWORD,
userFilter.apply(workflow, task, model),
passwordFilter.apply(workflow, task, model))
.getBytes())));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.serverlessworkflow.impl.WorkflowUtils;
import io.serverlessworkflow.impl.WorkflowValueResolver;
import java.net.URI;
import java.util.concurrent.CompletableFuture;

class BearerAuthProvider implements AuthProvider {

Expand All @@ -49,8 +50,9 @@ public BearerAuthProvider(
}

@Override
public String content(WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
return tokenFilter.apply(workflow, task, model);
public CompletableFuture<String> content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
return CompletableFuture.completedFuture(tokenFilter.apply(workflow, task, model));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

abstract class CommonOAuthProvider implements AuthProvider {
public abstract class CommonOAuthProvider implements AuthProvider {

private final WorkflowValueResolver<AccessTokenProvider> tokenProvider;

Expand All @@ -47,8 +48,12 @@ protected CommonOAuthProvider(WorkflowValueResolver<AccessTokenProvider> tokenPr
}

@Override
public String content(WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
return tokenProvider.apply(workflow, task, model).validateAndGet(workflow, task, model).token();
public CompletableFuture<String> content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
return tokenProvider
.apply(workflow, task, model)
.validateAndGet(workflow, task, model)
.thenApply(JWT::token);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
import io.serverlessworkflow.impl.WorkflowValueResolver;
import io.serverlessworkflow.impl.utils.RandomFactory;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -146,7 +146,8 @@ public String scheme() {
}

@Override
public String content(WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
public CompletableFuture<String> content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri) {
try {
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod(method);
Expand Down Expand Up @@ -186,17 +187,19 @@ public String content(WorkflowContext workflow, TaskContext task, WorkflowModel
ha2))
.orElseGet(() -> calculateHash(ha1, serverInfo.nonce, ha2));

return buildResponseInfo(serverInfo, userName, path, clientNonce, nonceCount, response);
return CompletableFuture.completedFuture(
buildResponseInfo(serverInfo, userName, path, clientNonce, nonceCount, response));
} else {
throw new IllegalStateException(
"URI "
+ uri
+ " is not digest protected, it returned code "
+ responseCode
+ " when invoked without authentication header, but it should have returned 401 as per RFC 2617");
return CompletableFuture.failedFuture(
new IllegalStateException(
"URI "
+ uri
+ " is not digest protected, it returned code "
+ responseCode
+ " when invoked without authentication header, but it should have returned 401 as per RFC 2617"));
}
} catch (IOException io) {
throw new UncheckedIOException(io);
return CompletableFuture.failedFuture(io);
}
}

Expand Down
76 changes: 63 additions & 13 deletions impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,79 @@
package io.serverlessworkflow.impl.auth;

import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public interface JWT {
public record JWT(String token, Map<String, Object> header, Map<String, Object> claims) {

String token();
public Optional<Instant> expiresAt() {
return toInstant(claims.get("exp"));
}

List<String> audience();
public Optional<Instant> issuedAt() {
return toInstant(claims.get("iat"));
}

Map<String, Object> claims();
public Collection<String> audience() {
return toCollection(claims.get("aud"), String.class);
}

<T> Optional<T> claim(String name, Class<T> type);
public Optional<String> issuer() {
return Optional.ofNullable((String) claims.get("iss"));
}

Optional<Instant> expiresAt();
public Optional<String> subject() {
return Optional.ofNullable((String) claims.get("sub"));
}

Map<String, Object> header();
public Optional<String> type() {
return header.containsKey("typ")
? Optional.of((String) header.get("typ"))
: Optional.ofNullable((String) claims.get("typ"));
}

Optional<Instant> issuedAt();
static Optional<Instant> toInstant(Object v) {
if (v == null) {
return Optional.empty();
}
if (v instanceof Instant i) {
return Optional.of(i);
}
if (v instanceof Number n) {
return Optional.of(Instant.ofEpochSecond((n.longValue())));
}
if (v instanceof String s) {
try {
long sec = Long.parseLong(s.trim());
return Optional.of(Instant.ofEpochSecond((sec)));
} catch (NumberFormatException ignored) {
try {
return Optional.of(Instant.parse(s.trim()));
} catch (DateTimeParseException ex) {
}
}
}
return Optional.empty();
}

Optional<String> issuer();

Optional<String> subject();

Optional<String> type();
/* Does not support primitive types intentionally, as they are not used in that context. */
static <T> Collection<T> toCollection(Object v, Class<T> clazz) {
if (v == null) {
return List.of();
}
if (clazz.isInstance(v)) {
return List.of(clazz.cast(v));
}
if (v instanceof Collection col) {
return col;
}
if (v.getClass().isArray() && v.getClass().getComponentType().equals(clazz)) {
return Arrays.asList((T[]) v);
}
return List.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.impl.WorkflowApplication;

class OAuth2AuthProvider extends CommonOAuthProvider {
public class OAuth2AuthProvider extends CommonOAuthProvider {

public OAuth2AuthProvider(
WorkflowApplication application, Workflow workflow, OAuthPolicyData policyData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.impl.WorkflowApplication;

class OpenIdAuthProvider extends CommonOAuthProvider {
public class OpenIdAuthProvider extends CommonOAuthProvider {

public OpenIdAuthProvider(
WorkflowApplication application, Workflow workflow, OAuthPolicyData policyData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,31 @@ public RunScriptExecutor(
@Override
public CompletableFuture<WorkflowModel> apply(
WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) {
ScriptContext scriptContext =
new ScriptContext(
argumentExpr.map(m -> m.apply(workflowContext, taskContext, input)).orElse(Map.of()),
environmentExpr.map(m -> m.apply(workflowContext, taskContext, input)).orElse(Map.of()),
codeSupplier.apply(workflowContext, taskContext, input),
returnType);
if (isAwait) {
return CompletableFuture.supplyAsync(
() -> runScript(scriptContext, workflowContext, taskContext, input),
() -> runScript(workflowContext, taskContext, input),
workflowContext.definition().application().executorService());
} else {
workflowContext
.definition()
.application()
.executorService()
.submit(() -> runScript(scriptContext, workflowContext, taskContext, input));
.submit(() -> runScript(workflowContext, taskContext, input));
return CompletableFuture.completedFuture(input);
}
}

private WorkflowModel runScript(
ScriptContext scriptContext,
WorkflowContext workflowContext,
TaskContext taskContext,
WorkflowModel input) {
WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) {
try {
ScriptContext scriptContext =
new ScriptContext(
argumentExpr.map(m -> m.apply(workflowContext, taskContext, input)).orElse(Map.of()),
environmentExpr
.map(m -> m.apply(workflowContext, taskContext, input))
.orElse(Map.of()),
codeSupplier.apply(workflowContext, taskContext, input),
returnType);
return taskRunner.runScript(scriptContext, workflowContext, taskContext, input);
} catch (Exception ex) {
throw new WorkflowException(WorkflowError.runtime(taskContext, ex).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ public <T> T load(
.map(
auth ->
AuthUtils.authHeaderValue(
auth.scheme(), auth.content(workflowContext, taskContext, model, uri))));
auth.scheme(),
auth.content(workflowContext, taskContext, model, uri).join())));
}

public <T> T loadURI(URI uri, Function<ExternalResourceHandler, T> function) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2020-Present The Serverless Workflow Specification Authors
*
* 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.
*/
package io.serverlessworkflow.impl.auth;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import org.junit.jupiter.api.Test;

public class JWTTest {

@Test
public void testToStringCollection() {
String[] test = {"javierito", "fulanito", "menganito"};
assertThat(JWT.toCollection(test, String.class))
.isEqualTo(List.of("javierito", "fulanito", "menganito"));
}

@Test
public void testToIntCollection() {
Integer[] test = {1, 2, 3};
assertThat(JWT.toCollection(test, Integer.class)).isEqualTo(List.of(1, 2, 3));
}
}
Loading
Loading