Skip to content
Open
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,10 +18,12 @@
import io.serverlessworkflow.impl.TaskContext;
import io.serverlessworkflow.impl.WorkflowContext;
import io.serverlessworkflow.impl.WorkflowModel;
import java.net.URI;

public interface AuthProvider {

String authScheme();
String scheme();

String authParameter(WorkflowContext workflow, TaskContext task, WorkflowModel model);
String content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri, String method);
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ private static Optional<AuthProvider> buildFromPolicy(
new BearerAuthProvider(
app, workflow, authenticationPolicy.getBearerAuthenticationPolicy()));
} else if (authenticationPolicy.getDigestAuthenticationPolicy() != null) {
// TODO implement digest authentication
return Optional.empty();
//
return Optional.of(
new DigestAuthProvider(
app, workflow, authenticationPolicy.getDigestAuthenticationPolicy()));
} else if (authenticationPolicy.getOAuth2AuthenticationPolicy() != null) {
return Optional.of(
new OAuth2AuthProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.serverlessworkflow.impl.WorkflowModel;
import io.serverlessworkflow.impl.WorkflowUtils;
import io.serverlessworkflow.impl.WorkflowValueResolver;
import java.net.URI;
import java.util.Base64;

class BasicAuthProvider implements AuthProvider {
Expand Down Expand Up @@ -57,7 +58,8 @@ public BasicAuthProvider(
}

@Override
public String authParameter(WorkflowContext workflow, TaskContext task, WorkflowModel model) {
public String content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri, String method) {
return new String(
Base64.getEncoder()
.encode(
Expand All @@ -69,7 +71,7 @@ public String authParameter(WorkflowContext workflow, TaskContext task, Workflow
}

@Override
public String authScheme() {
public String scheme() {
return "Basic";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.serverlessworkflow.impl.WorkflowModel;
import io.serverlessworkflow.impl.WorkflowUtils;
import io.serverlessworkflow.impl.WorkflowValueResolver;
import java.net.URI;

class BearerAuthProvider implements AuthProvider {

Expand All @@ -48,12 +49,13 @@ public BearerAuthProvider(
}

@Override
public String authParameter(WorkflowContext workflow, TaskContext task, WorkflowModel model) {
public String content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri, String method) {
return tokenFilter.apply(workflow, task, model);
}

@Override
public String authScheme() {
public String scheme() {
return "Bearer";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.serverlessworkflow.impl.WorkflowContext;
import io.serverlessworkflow.impl.WorkflowModel;
import io.serverlessworkflow.impl.WorkflowValueResolver;
import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import java.util.ServiceLoader;
Expand All @@ -48,12 +49,13 @@ protected CommonOAuthProvider(WorkflowValueResolver<AccessTokenProvider> tokenPr
}

@Override
public String authParameter(WorkflowContext workflow, TaskContext task, WorkflowModel model) {
public String content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri, String method) {
return tokenProvider.apply(workflow, task, model).validateAndGet(workflow, task, model).token();
}

@Override
public String authScheme() {
public String scheme() {
return "Bearer";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* 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 io.serverlessworkflow.impl.WorkflowUtils.checkSecret;
import static io.serverlessworkflow.impl.WorkflowUtils.secretProp;
import static io.serverlessworkflow.impl.auth.AuthUtils.PASSWORD;
import static io.serverlessworkflow.impl.auth.AuthUtils.USER;

import io.serverlessworkflow.api.types.DigestAuthenticationPolicy;
import io.serverlessworkflow.api.types.DigestAuthenticationProperties;
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.impl.TaskContext;
import io.serverlessworkflow.impl.WorkflowApplication;
import io.serverlessworkflow.impl.WorkflowContext;
import io.serverlessworkflow.impl.WorkflowModel;
import io.serverlessworkflow.impl.WorkflowUtils;
import io.serverlessworkflow.impl.WorkflowValueResolver;
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.StringTokenizer;
import java.util.concurrent.atomic.AtomicInteger;

class DigestAuthProvider implements AuthProvider {

private static final String NONCE = "nonce";
private static final String REALM = "realm";
private static final String QOP_KEY = "qop";
private static final String OPAQUE = "opaque";

private static class DigestServerInfo {

private Algorithm algorithm = Algorithm.MD5;
private String nonce;
private String opaque;
private String realm;
private QOP qop = null;

public static DigestServerInfo from(String header) {
DigestServerInfo serverInfo = new DigestServerInfo();
StringTokenizer tokenizer = new StringTokenizer(header);
while (tokenizer.hasMoreElements()) {
String token = tokenizer.nextToken();

int indexOf = token.indexOf("=");
if (indexOf != -1) {
String key = token.substring(0, indexOf).trim().toLowerCase();
String value = token.substring(indexOf + 1).trim();
switch (key) {
case "algorithm":
serverInfo.algorithm = Algorithm.valueOf(value.toUpperCase());
break;
case NONCE:
serverInfo.nonce = value;
break;
case OPAQUE:
serverInfo.opaque = value;
break;
case REALM:
serverInfo.realm = value;
break;
case QOP_KEY:
serverInfo.qop = QOP.valueOf(value.toUpperCase());
break;
}
}
}
return serverInfo;
}
}

private static enum Algorithm {
MD5,
MD5SESSS
};

private static enum QOP {
AUTH,
AUTH_INT,
};

private final WorkflowValueResolver<String> userFilter;
private final WorkflowValueResolver<String> passwordFilter;

public DigestAuthProvider(
WorkflowApplication app, Workflow workflow, DigestAuthenticationPolicy authPolicy) {
DigestAuthenticationProperties properties =
authPolicy.getDigest().getDigestAuthenticationProperties();
if (properties != null) {
userFilter = WorkflowUtils.buildStringFilter(app, properties.getUsername());
passwordFilter = WorkflowUtils.buildStringFilter(app, properties.getPassword());
} else if (authPolicy.getDigest().getDigestAuthenticationPolicySecret() != null) {
String secretName =
checkSecret(workflow, authPolicy.getDigest().getDigestAuthenticationPolicySecret());
userFilter = (w, t, m) -> secretProp(w, secretName, USER);
passwordFilter = (w, t, m) -> secretProp(w, secretName, PASSWORD);
} else {
throw new IllegalStateException(
"Both secret and properties are null for digest authorization");
}
}

@Override
public String scheme() {
return "Digest";
}

@Override
public String content(
WorkflowContext workflow, TaskContext task, WorkflowModel model, URI uri, String method) {
try {
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod(method);
int responseCode = connection.getResponseCode();
if (responseCode == 401) {
DigestServerInfo serverInfo =
DigestServerInfo.from(connection.getHeaderField("WWW-Authenticate"));
String userName = userFilter.apply(workflow, task, model);
String path = uri.getPath();
String ha1 =
calculateHash(userName, serverInfo.realm, passwordFilter.apply(workflow, task, model));

String nonceCount;
String clientNonce;
if (serverInfo.qop == QOP.AUTH
|| serverInfo.qop == QOP.AUTH_INT
|| serverInfo.algorithm == Algorithm.MD5SESSS) {
nonceCount = Integer.toString(nc.getAndIncrement());
clientNonce = getClientNonce(nonceCount);
} else {
nonceCount = null;
clientNonce = null;
}
String response;
if (serverInfo.algorithm == Algorithm.MD5SESSS) {
ha1 = calculateHash(ha1, serverInfo.nonce, clientNonce);
}
String ha2 = calculateHash(String.format("%s:%s", method, uri));
if (serverInfo.qop == QOP.AUTH || serverInfo.qop == QOP.AUTH_INT) {
response =
calculateHash(
ha1,
serverInfo.nonce,
nonceCount,
clientNonce,
serverInfo.qop.toString().toLowerCase(),
ha2);
} else {
response = calculateHash(ha1, serverInfo.nonce, ha2);
}

return 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");
}
} catch (IOException io) {
throw new UncheckedIOException(io);
}
}

private String buildResponseInfo(
DigestServerInfo digestInfo,
String userName,
String uri,
String clientNonce,
String nonceCount,
String response) {
StringBuilder sb = new StringBuilder("username=" + userName);
addHeader(sb, "uri", uri);
addHeader(sb, "response", response);
addHeader(sb, NONCE, digestInfo.nonce);
addHeader(sb, REALM, digestInfo.realm);
if (digestInfo.opaque != null) {
addHeader(sb, OPAQUE, digestInfo.opaque);
}
if (digestInfo.qop != null) {
addHeader(sb, QOP_KEY, digestInfo.qop.toString());
}
if (clientNonce != null) {
addHeader(sb, "cnonce", clientNonce);
addHeader(sb, "nc", nonceCount);
}
return sb.toString();
}

private StringBuilder addHeader(StringBuilder sb, String key, String value) {
return sb.append(',').append(key).append('=').append(value);
}

private static AtomicInteger nc = new AtomicInteger(1);

private static String getClientNonce(String nonceCount) {
return "impl-" + nonceCount;
}

private String calculateHash(String firstOne, String... strs) {
try {

MessageDigest md = MessageDigest.getInstance("MD5");
StringBuilder sb = new StringBuilder(firstOne);
for (String str : strs) {
sb.append(':').append(str);
}
return new String(md.digest(sb.toString().getBytes()));
} catch (NoSuchAlgorithmException ex) {
throw new UnsupportedOperationException("System is not supporting MD5!!!!", ex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,22 @@ public <T> T load(
WorkflowContext workflowContext,
TaskContext taskContext,
WorkflowModel model) {
return loadURI(
URI uri =
uriSupplier(endPoint)
.apply(
workflowContext,
taskContext,
model == null ? application.modelFactory().fromNull() : model),
model == null ? application.modelFactory().fromNull() : model);
return loadURI(
uri,
function,
AuthProviderFactory.getAuth(
workflowContext.definition(), endPoint.getEndpointConfiguration())
.map(
auth ->
AuthUtils.authHeaderValue(
auth.authScheme(),
auth.authParameter(workflowContext, taskContext, model))));
auth.scheme(),
auth.content(workflowContext, taskContext, model, uri, "GET"))));
}

public <T> T loadURI(URI uri, Function<ExternalResourceHandler, T> function) {
Expand Down
Loading