Skip to content

Commit

Permalink
[eclipse-ditto#926] add HTTP API for activateTokenIntegration and dea…
Browse files Browse the repository at this point in the history
…ctivateTokenIntegration.

Signed-off-by: Yufei Cai <yufei.cai@bosch.io>
  • Loading branch information
yufei-cai committed Dec 29, 2020
1 parent a2b7f19 commit 3fc926d
Show file tree
Hide file tree
Showing 22 changed files with 681 additions and 45 deletions.
Expand Up @@ -69,7 +69,7 @@ static Audience fromJson(final JsonValue audValue) {
*
* @return the empty audience.
*/
static Audience empty() {
public static Audience empty() {
return new Audience(Collections.emptyList());
}

Expand Down
Expand Up @@ -70,15 +70,15 @@ default PipelineElement resolve(final String expressionTemplate) {
* @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) {
default String 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 + "}}");
}
});
}).toOptional().orElseThrow(() -> new IllegalStateException("Impossible"));
}

/**
Expand Down
Expand Up @@ -125,7 +125,7 @@ public void testDeleteIfUnresolved() {
@Test
public void testPartialResolution() {
assertThat(expressionResolver.resolvePartially("{{header:header-name}}-{{unknown:placeholder|fn:unknown}}"))
.containsExactly("header-val-{{unknown:placeholder|fn:unknown}}");
.isEqualTo("header-val-{{unknown:placeholder|fn:unknown}}");
}

}
Expand Up @@ -39,11 +39,17 @@ public final class PolicyActionFailedException extends DittoRuntimeException imp
*/
public static final String ERROR_CODE = ERROR_CODE_PREFIX + "action.failed";

private static final HttpStatusCode DEFAULT_STATUS = HttpStatusCode.INTERNAL_SERVER_ERROR;
/**
* The action {@code activateTokenIntegration}.
*/
public static final String ACTIVATE_TOKEN_INTEGRATION = "activateTokenIntegration";

private static final String ACTIVATE_TOKEN_INTEGRATION = "activateTokenIntegration";
/**
* The action {@code deactivateTokenIntegration}.
*/
public static final String DEACTIVATE_TOKEN_INTEGRATION = "deactivateTokenIntegration";

private static final String DEACTIVATE_TOKEN_INTEGRATION = "deactivateTokenIntegration";
private static final HttpStatusCode DEFAULT_STATUS = HttpStatusCode.INTERNAL_SERVER_ERROR;

private static final String MESSAGE_TEMPLATE = "Failed to execute action ''{0}''.";

Expand Down Expand Up @@ -76,6 +82,19 @@ public static DittoRuntimeExceptionBuilder<PolicyActionFailedException> newBuild
return new Builder().action(DEACTIVATE_TOKEN_INTEGRATION);
}

/**
* A mutable builder for a {@code PolicyActionFailedException} due to inappropriate authentication method.
*
* @param action the failed action.
* @return the exception builder.
*/
public static DittoRuntimeExceptionBuilder<PolicyActionFailedException>
newBuilderForInappropriateAuthenticationMethod(final String action) {
return new Builder().action(action)
.status(HttpStatusCode.BAD_REQUEST)
.description("Policy action is only possible with JWT authentication.");
}

/**
* A mutable builder for when a deactivation failed due to matching permanent subjects.
*
Expand Down
Expand Up @@ -74,10 +74,10 @@ public GatewayAuthenticationDirective(final AuthenticationChain authenticationCh
* Depending on the request headers, one of the supported authentication mechanisms is applied.
*
* @param dittoHeaders the DittoHeaders containing already gathered context information.
* @param inner the inner route which will be wrapped with the {@link DittoHeaders}.
* @param inner the inner route which will be wrapped with the {@link org.eclipse.ditto.model.base.headers.DittoHeaders}.
* @return the inner route.
*/
public Route authenticate(final DittoHeaders dittoHeaders, final Function<DittoHeadersBuilder<?, ?>, Route> inner) {
public Route authenticate(final DittoHeaders dittoHeaders, final Function<AuthenticationResult, Route> inner) {
return extractRequestContext(requestContext -> {
final Uri requestUri = requestContext.getRequest().getUri();

Expand All @@ -95,12 +95,12 @@ public Route authenticate(final DittoHeaders dittoHeaders, final Function<DittoH
private Route handleAuthenticationTry(final Try<AuthenticationResult> authenticationResultTry,
final Uri requestUri,
final DittoHeaders dittoHeaders,
final Function<DittoHeadersBuilder<?, ?>, Route> inner) {
final Function<AuthenticationResult, Route> inner) {

if (authenticationResultTry.isSuccess()) {
final AuthenticationResult authenticationResult = authenticationResultTry.get();
if (authenticationResult.isSuccess()) {
return inner.apply(authenticationResult.getDittoHeaders().toBuilder());
return inner.apply(authenticationResult);
}
return handleFailedAuthentication(authenticationResult.getReasonOfFailure(), requestUri, dittoHeaders);
}
Expand Down
Expand Up @@ -27,7 +27,6 @@

import org.eclipse.ditto.model.base.exceptions.DittoRuntimeException;
import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.base.headers.DittoHeadersBuilder;
import org.eclipse.ditto.model.base.headers.DittoHeadersSizeChecker;
import org.eclipse.ditto.model.base.json.JsonSchemaVersion;
import org.eclipse.ditto.protocoladapter.HeaderTranslator;
Expand All @@ -50,6 +49,7 @@
import org.eclipse.ditto.services.gateway.endpoints.routes.websocket.WebSocketRouteBuilder;
import org.eclipse.ditto.services.gateway.endpoints.routes.whoami.WhoamiRoute;
import org.eclipse.ditto.services.gateway.endpoints.utils.DittoRejectionHandlerFactory;
import org.eclipse.ditto.services.gateway.security.authentication.AuthenticationResult;
import org.eclipse.ditto.services.gateway.util.config.endpoints.HttpConfig;
import org.eclipse.ditto.services.utils.health.routes.StatusRoute;
import org.eclipse.ditto.services.utils.protocol.ProtocolAdapterProvider;
Expand Down Expand Up @@ -219,16 +219,17 @@ private Route api(final RequestContext ctx, final CharSequence correlationId,
return rawPathPrefix(PathMatchers.slash().concat(HTTP_PATH_API_PREFIX), () -> // /api
ensureSchemaVersion(apiVersion -> // /api/<apiVersion>
customApiRoutesProvider.unauthorized(apiVersion, correlationId).orElse(
apiAuthentication(apiVersion, correlationId, initialHeadersBuilder -> {
apiAuthentication(apiVersion, correlationId, auth -> {
final CompletionStage<DittoHeaders> dittoHeadersPromise =
rootRouteHeadersStepBuilder
.withInitialDittoHeadersBuilder(initialHeadersBuilder)
.withInitialDittoHeadersBuilder(
auth.getDittoHeaders().toBuilder())
.withRequestContext(ctx)
.withQueryParameters(queryParameters)
.build(CustomHeadersHandler.RequestType.API);

return withDittoHeaders(dittoHeadersPromise,
dittoHeaders -> buildApiSubRoutes(ctx, dittoHeaders));
dittoHeaders -> buildApiSubRoutes(ctx, dittoHeaders, auth));
}
)
)
Expand All @@ -255,7 +256,7 @@ private Route ensureSchemaVersion(final Function<JsonSchemaVersion, Route> inner
}

private Route apiAuthentication(final JsonSchemaVersion schemaVersion, final CharSequence correlationId,
final Function<DittoHeadersBuilder<?, ?>, Route> inner) {
final Function<AuthenticationResult, Route> inner) {

final DittoHeaders dittoHeaders = DittoHeaders.newBuilder()
.schemaVersion(schemaVersion)
Expand All @@ -264,13 +265,14 @@ private Route apiAuthentication(final JsonSchemaVersion schemaVersion, final Cha
return apiAuthenticationDirective.authenticate(dittoHeaders, inner);
}

private Route buildApiSubRoutes(final RequestContext ctx, final DittoHeaders dittoHeaders) {
private Route buildApiSubRoutes(final RequestContext ctx, final DittoHeaders dittoHeaders,
final AuthenticationResult authenticationResult) {

final Route customApiSubRoutes = customApiRoutesProvider.authorized(dittoHeaders);

return concat(
// /api/{apiVersion}/policies
policiesRoute.buildPoliciesRoute(ctx, dittoHeaders),
policiesRoute.buildPoliciesRoute(ctx, dittoHeaders, authenticationResult),
// /api/{apiVersion}/things SSE support
buildSseThingsRoute(ctx, dittoHeaders),
// /api/{apiVersion}/things
Expand Down Expand Up @@ -309,10 +311,10 @@ private Route ws(final RequestContext ctx, final CharSequence correlationId,
final Map<String, String> queryParameters) {
return rawPathPrefix(PathMatchers.slash().concat(WS_PATH_PREFIX), () -> // /ws
ensureSchemaVersion(wsVersion -> // /ws/<wsVersion>
wsAuthentication(wsVersion, correlationId, initialHeadersBuilder -> {
wsAuthentication(wsVersion, correlationId, auth -> {
final CompletionStage<DittoHeaders> dittoHeadersPromise =
rootRouteHeadersStepBuilder
.withInitialDittoHeadersBuilder(initialHeadersBuilder)
.withInitialDittoHeadersBuilder(auth.getDittoHeaders().toBuilder())
.withRequestContext(ctx)
.withQueryParameters(queryParameters)
.build(CustomHeadersHandler.RequestType.WS);
Expand All @@ -331,7 +333,7 @@ private Route ws(final RequestContext ctx, final CharSequence correlationId,
}

private Route wsAuthentication(final JsonSchemaVersion schemaVersion, final CharSequence correlationId,
final Function<DittoHeadersBuilder<?, ?>, Route> inner) {
final Function<AuthenticationResult, Route> inner) {

final DittoHeaders dittoHeaders = DittoHeaders.newBuilder()
.schemaVersion(schemaVersion)
Expand Down
Expand Up @@ -40,7 +40,7 @@
* This class provides an {@link ExceptionHandler} for the root route.
*/
@Immutable
final class RootRouteExceptionHandler {
public final class RootRouteExceptionHandler {

private static final ThreadSafeDittoLogger LOGGER = DittoLoggerFactory
.getThreadSafeLogger(RootRouteExceptionHandler.class);
Expand Down
@@ -0,0 +1,52 @@
/*
* 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.endpoints.routes.policies;

import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.jwt.JsonWebToken;
import org.eclipse.ditto.model.placeholders.ExpressionResolver;
import org.eclipse.ditto.model.placeholders.PlaceholderFactory;
import org.eclipse.ditto.model.policies.SubjectId;
import org.eclipse.ditto.services.gateway.security.authentication.jwt.JwtPlaceholder;
import org.eclipse.ditto.services.gateway.util.config.security.OAuthConfig;

/**
* Creator of token integration subjects for the configured OAuth subject pattern.
*/
public final class OAuthTokenIntegrationSubjectIdFactory implements TokenIntegrationSubjectIdFactory {

private final String subjectTemplate;

private OAuthTokenIntegrationSubjectIdFactory(final String subjectTemplate) {
this.subjectTemplate = subjectTemplate;
}

/**
* Create an OAuth token integration subject ID factory from OAuth config.
*
* @param oAuthConfig the config.
* @return the factory.
*/
public static OAuthTokenIntegrationSubjectIdFactory of(final OAuthConfig oAuthConfig) {
return new OAuthTokenIntegrationSubjectIdFactory(oAuthConfig.getTokenIntegrationSubject());
}

@Override
public SubjectId getSubjectId(final DittoHeaders dittoHeaders, final JsonWebToken jwt) {
final ExpressionResolver expressionResolver = PlaceholderFactory.newExpressionResolver(
PlaceholderFactory.newPlaceholderResolver(PlaceholderFactory.newHeadersPlaceholder(), dittoHeaders),
PlaceholderFactory.newPlaceholderResolver(JwtPlaceholder.getInstance(), jwt)
);
return SubjectId.newInstance(expressionResolver.resolvePartially(subjectTemplate));
}
}

0 comments on commit 3fc926d

Please sign in to comment.