Skip to content

Commit

Permalink
[eclipse-ditto#926] extend authentication result to include JWT.
Browse files Browse the repository at this point in the history
Signed-off-by: Yufei Cai <yufei.cai@bosch.io>
  • Loading branch information
yufei-cai committed Dec 29, 2020
1 parent 474f077 commit a2b7f19
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 99 deletions.
Expand Up @@ -60,6 +60,27 @@ default PipelineElement resolve(final String expressionTemplate) {
return ExpressionResolver.substitute(expressionTemplate, this::resolveAsPipelineElement);
}

/**
* Resolves a complete expression template starting with a {@link Placeholder} followed by optional pipeline stages
* (e.g. functions). Keep unresolvable expressions as is.
*
* @param expressionTemplate the expressionTemplate to resolve {@link org.eclipse.ditto.model.placeholders.Placeholder}s and and execute optional
* pipeline stages
* @return the resolved String, a signifier for resolution failure, or one for deletion.
* @throws PlaceholderFunctionTooComplexException thrown if the {@code expressionTemplate} contains a placeholder
* function chain which is too complex (e.g. too much chained function calls)
*/
default PipelineElement resolvePartially(final String expressionTemplate) {
return ExpressionResolver.substitute(expressionTemplate, expression -> {
try {
return resolveAsPipelineElement(expression).onUnresolved(() -> PipelineElement.resolved(expression));
} catch (final UnresolvedPlaceholderException e) {
// placeholder is not supported; return the expression without resolution.
return PipelineElement.resolved("{{" + expression + "}}");
}
});
}

/**
* Perform simple substitution on a string based on a template function.
*
Expand Down
Expand Up @@ -122,4 +122,10 @@ public void testDeleteIfUnresolved() {
.isEqualTo(PipelineElement.deleted());
}

@Test
public void testPartialResolution() {
assertThat(expressionResolver.resolvePartially("{{header:header-name}}-{{unknown:placeholder|fn:unknown}}"))
.containsExactly("header-val-{{unknown:placeholder|fn:unknown}}");
}

}
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.services.gateway.security.authentication;

import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull;

import java.util.Objects;

import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

import org.eclipse.ditto.model.base.auth.AuthorizationContext;
import org.eclipse.ditto.model.base.headers.DittoHeaders;

/**
* Abstract implementation of an authentication result that bears an {@link org.eclipse.ditto.model.base.auth.AuthorizationContext}.
*/
@NotThreadSafe
public abstract class AbstractAuthenticationResult implements AuthenticationResult {

private final DittoHeaders dittoHeaders;
@Nullable private final AuthorizationContext authorizationContext;
@Nullable private final Throwable reasonOfFailure;

/**
* Construct an authentication result.
*
* @param dittoHeaders the Ditto headers without authorization context.
* @param authorizationContext the authorization context.
* @param reasonOfFailure the reason for authentication failure or null if authentication is successful.
*/
protected AbstractAuthenticationResult(final DittoHeaders dittoHeaders,
@Nullable final AuthorizationContext authorizationContext, @Nullable final Throwable reasonOfFailure) {

checkNotNull(dittoHeaders, "dittoHeaders");
if (null != authorizationContext) {
// merge present authorizationContext into dittoHeaders:
this.dittoHeaders = dittoHeaders.toBuilder()
.authorizationContext(authorizationContext)
.build();
} else {
this.dittoHeaders = dittoHeaders;
}
this.authorizationContext = authorizationContext;
this.reasonOfFailure = reasonOfFailure;
}

@Override
public boolean isSuccess() {
return null != authorizationContext;
}

@Override
public AuthorizationContext getAuthorizationContext() {
if (null == authorizationContext) {
if (reasonOfFailure instanceof RuntimeException) {
throw (RuntimeException) reasonOfFailure;
}
throw new IllegalStateException(reasonOfFailure);
}

return authorizationContext;
}

@Override
public DittoHeaders getDittoHeaders() {
return dittoHeaders;
}

@Override
public Throwable getReasonOfFailure() {
if (null == reasonOfFailure) {
throw new IllegalStateException("Authentication was successful!");
}

return reasonOfFailure;
}

@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final AbstractAuthenticationResult that = (AbstractAuthenticationResult) o;
return Objects.equals(dittoHeaders, that.dittoHeaders) &&
Objects.equals(authorizationContext, that.authorizationContext) &&
Objects.equals(reasonOfFailure, that.reasonOfFailure);
}

@Override
public int hashCode() {
return Objects.hash(dittoHeaders, authorizationContext, reasonOfFailure);
}

@Override
public String toString() {
return "dittoHeaders=" + dittoHeaders +
", authorizationContext=" + authorizationContext +
", reasonOfFailure=" + reasonOfFailure;
}

}
Expand Up @@ -12,6 +12,8 @@
*/
package org.eclipse.ditto.services.gateway.security.authentication;

import javax.annotation.Nullable;

import org.eclipse.ditto.model.base.auth.AuthorizationContext;
import org.eclipse.ditto.model.base.headers.DittoHeaders;

Expand All @@ -35,6 +37,7 @@ public interface AuthenticationResult {
* @throws java.lang.RuntimeException the reason of failure if this method is called when {@link #isSuccess()}
* evaluates to {@code false}.
*/
@Nullable
AuthorizationContext getAuthorizationContext();

/**
Expand All @@ -53,6 +56,7 @@ public interface AuthenticationResult {
* @throws java.lang.IllegalStateException if this methods is called when {@link #isSuccess()} evaluates to
* {@code true}.
*/
@Nullable
Throwable getReasonOfFailure();

}
Expand Up @@ -14,8 +14,6 @@

import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull;

import java.util.Objects;

import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

Expand All @@ -26,26 +24,11 @@
* Default implementation of an authentication result that bears an {@link AuthorizationContext}.
*/
@NotThreadSafe
public final class DefaultAuthenticationResult implements AuthenticationResult {

private final DittoHeaders dittoHeaders;
@Nullable private final AuthorizationContext authorizationContext;
@Nullable private final Throwable reasonOfFailure;
public final class DefaultAuthenticationResult extends AbstractAuthenticationResult {

private DefaultAuthenticationResult(final DittoHeaders dittoHeaders,
@Nullable final AuthorizationContext authorizationContext, @Nullable final Throwable reasonOfFailure) {

checkNotNull(dittoHeaders, "dittoHeaders");
if (null != authorizationContext) {
// merge present authorizationContext into dittoHeaders:
this.dittoHeaders = dittoHeaders.toBuilder()
.authorizationContext(authorizationContext)
.build();
} else {
this.dittoHeaders = dittoHeaders;
}
this.authorizationContext = authorizationContext;
this.reasonOfFailure = reasonOfFailure;
super(dittoHeaders, authorizationContext, reasonOfFailure);
}

/**
Expand Down Expand Up @@ -75,63 +58,9 @@ public static AuthenticationResult failed(final DittoHeaders dittoHeaders, final
return new DefaultAuthenticationResult(dittoHeaders, null, checkNotNull(reasonOfFailure, "reasonOfFailure"));
}

@Override
public boolean isSuccess() {
return null != authorizationContext;
}

@Override
public AuthorizationContext getAuthorizationContext() {
if (null == authorizationContext) {
if (reasonOfFailure instanceof RuntimeException) {
throw (RuntimeException) reasonOfFailure;
}
throw new IllegalStateException(reasonOfFailure);
}

return authorizationContext;
}

@Override
public DittoHeaders getDittoHeaders() {
return dittoHeaders;
}

@Override
public Throwable getReasonOfFailure() {
if (null == reasonOfFailure) {
throw new IllegalStateException("Authentication was successful!");
}

return reasonOfFailure;
}

@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final DefaultAuthenticationResult that = (DefaultAuthenticationResult) o;
return Objects.equals(dittoHeaders, that.dittoHeaders) &&
Objects.equals(authorizationContext, that.authorizationContext) &&
Objects.equals(reasonOfFailure, that.reasonOfFailure);
}

@Override
public int hashCode() {
return Objects.hash(dittoHeaders, authorizationContext, reasonOfFailure);
}

@Override
public String toString() {
return getClass().getSimpleName() + " [" +
"dittoHeaders=" + dittoHeaders +
", authorizationContext=" + authorizationContext +
", reasonOfFailure=" + reasonOfFailure +
"]";
return getClass().getSimpleName() + " [" + super.toString() + "]";
}

}
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.services.gateway.security.authentication.jwt;

import java.util.Objects;
import java.util.Optional;

import javax.annotation.Nullable;

import org.eclipse.ditto.model.base.auth.AuthorizationContext;
import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.jwt.JsonWebToken;
import org.eclipse.ditto.services.gateway.security.authentication.AbstractAuthenticationResult;

/**
* Implementation of JwtAuthenticationResult.
*/
final class DefaultJwtAuthenticationResult extends AbstractAuthenticationResult implements JwtAuthenticationResult {

@Nullable
private final JsonWebToken jwt;

DefaultJwtAuthenticationResult(final DittoHeaders dittoHeaders,
@Nullable final AuthorizationContext authorizationContext,
@Nullable final Throwable reasonOfFailure,
@Nullable final JsonWebToken jwt) {
super(dittoHeaders, authorizationContext, reasonOfFailure);
this.jwt = jwt;
}

@Override
public Optional<JsonWebToken> getJwt() {
return Optional.ofNullable(jwt);
}

@Override
public boolean equals(final Object that) {
return super.equals(that) && Objects.equals(jwt, ((DefaultJwtAuthenticationResult) that).jwt);
}
}
Expand Up @@ -23,8 +23,6 @@
import org.eclipse.ditto.model.base.auth.DittoAuthorizationContextType;
import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.jwt.JsonWebToken;
import org.eclipse.ditto.services.gateway.security.authentication.AuthenticationResult;
import org.eclipse.ditto.services.gateway.security.authentication.DefaultAuthenticationResult;

/**
* Default implementation of {@link JwtAuthenticationResultProvider}.
Expand All @@ -42,7 +40,7 @@ private DefaultJwtAuthenticationResultProvider(final JwtAuthorizationSubjectsPro
* Creates a new instance of the default JWT context provider with the given authorization subjects provider.
*
* @param authorizationSubjectsProvider used to extract authorization subjects from each {@link JsonWebToken JWT}
* passed to {@link #getAuthenticationResult(JsonWebToken,DittoHeaders)}.
* passed to {@link #getAuthenticationResult(JsonWebToken, DittoHeaders)}.
* @return the created instance.
* @throws NullPointerException if {@code authorizationSubjectsProvider} is {@code null}.
*/
Expand All @@ -54,10 +52,11 @@ public static DefaultJwtAuthenticationResultProvider of(
}

@Override
public AuthenticationResult getAuthenticationResult(final JsonWebToken jwt, final DittoHeaders dittoHeaders) {
public JwtAuthenticationResult getAuthenticationResult(final JsonWebToken jwt, final DittoHeaders dittoHeaders) {
final List<AuthorizationSubject> authSubjects = authSubjectsProvider.getAuthorizationSubjects(jwt);
return DefaultAuthenticationResult.successful(dittoHeaders,
AuthorizationModelFactory.newAuthContext(DittoAuthorizationContextType.JWT, authSubjects));
return JwtAuthenticationResult.successful(dittoHeaders,
AuthorizationModelFactory.newAuthContext(DittoAuthorizationContextType.JWT, authSubjects),
jwt);
}

}

0 comments on commit a2b7f19

Please sign in to comment.