-
Notifications
You must be signed in to change notification settings - Fork 214
/
PreAuthenticatedAuthenticationProvider.java
169 lines (140 loc) · 7.56 KB
/
PreAuthenticatedAuthenticationProvider.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
* Copyright (c) 2017 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.gateway.service.security.authentication.preauth;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import javax.annotation.concurrent.Immutable;
import org.eclipse.ditto.base.model.auth.AuthorizationContext;
import org.eclipse.ditto.base.model.auth.AuthorizationContextType;
import org.eclipse.ditto.base.model.auth.AuthorizationModelFactory;
import org.eclipse.ditto.base.model.auth.AuthorizationSubject;
import org.eclipse.ditto.base.model.auth.DittoAuthorizationContextType;
import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.gateway.api.GatewayAuthenticationFailedException;
import org.eclipse.ditto.gateway.service.security.HttpHeader;
import org.eclipse.ditto.gateway.service.security.authentication.AuthenticationResult;
import org.eclipse.ditto.gateway.service.security.authentication.DefaultAuthenticationResult;
import org.eclipse.ditto.gateway.service.security.authentication.TimeMeasuringAuthenticationProvider;
import org.eclipse.ditto.gateway.service.security.utils.HttpUtils;
import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;
import org.eclipse.ditto.utils.jsr305.annotations.AllValuesAreNonnullByDefault;
import org.apache.pekko.http.javadsl.model.HttpRequest;
import org.apache.pekko.http.javadsl.model.Query;
import org.apache.pekko.http.javadsl.model.Uri;
import org.apache.pekko.http.javadsl.server.RequestContext;
/**
* Handles authentication by using a defined header field {@link org.eclipse.ditto.gateway.service.security.HttpHeader#X_DITTO_PRE_AUTH} which proxies in front
* of Ditto may set to inject authenticated subjects into a HTTP request.
* <p>
* Enabled/disabled by
* {@link org.eclipse.ditto.gateway.service.util.config.security.AuthenticationConfig#isPreAuthenticationEnabled()}.
* </p>
* If this is enabled it is of upmost importance that only the proxy in front of Ditto sets the defined header field
* {@link org.eclipse.ditto.gateway.service.security.HttpHeader#X_DITTO_PRE_AUTH}, otherwise anyone using the HTTP API may impersonate any other authorization
* subject.
*
* @since 1.1.0
*/
@Immutable
@AllValuesAreNonnullByDefault
public final class PreAuthenticatedAuthenticationProvider
extends TimeMeasuringAuthenticationProvider<AuthenticationResult> {
private static final ThreadSafeDittoLogger LOGGER =
DittoLoggerFactory.getThreadSafeLogger(PreAuthenticatedAuthenticationProvider.class);
private static final PreAuthenticatedAuthenticationProvider INSTANCE = new PreAuthenticatedAuthenticationProvider();
private PreAuthenticatedAuthenticationProvider() {
super(LOGGER);
}
/**
* Creates a new instance of the JWT authentication provider.
*
* @return the created instance.
*/
public static PreAuthenticatedAuthenticationProvider getInstance() {
return INSTANCE;
}
@Override
public boolean isApplicable(final RequestContext requestContext) {
return containsHeader(requestContext, HttpHeader.X_DITTO_PRE_AUTH);
}
private static boolean containsHeader(final RequestContext requestContext, final HttpHeader header) {
final Optional<String> requestHeader = HttpUtils.getRequestHeader(requestContext, header.getName());
return requestHeader.isPresent() || getRequestParam(requestContext, header).isPresent();
}
private static Optional<String> getRequestParam(final RequestContext requestContext, final HttpHeader header) {
final HttpRequest httpRequest = requestContext.getRequest();
final Uri requestUri = httpRequest.getUri();
final Query query = requestUri.query();
return query.get(header.getName());
}
@Override
protected CompletableFuture<AuthenticationResult> tryToAuthenticate(final RequestContext requestContext,
final DittoHeaders dittoHeaders) {
final Optional<String> preAuthOpt = getPreAuthenticated(requestContext);
if (preAuthOpt.isEmpty()) {
return CompletableFuture.completedFuture(
DefaultAuthenticationResult.failed(dittoHeaders, getAuthenticationFailedException(dittoHeaders)));
}
final String preAuthenticatedSubject = preAuthOpt.get();
final List<AuthorizationSubject> authorizationSubjects = getAuthorizationSubjects(preAuthenticatedSubject);
if (authorizationSubjects.isEmpty()) {
return CompletableFuture.completedFuture(toFailedAuthenticationResult(
buildFailedToExtractAuthorizationSubjectsException(preAuthenticatedSubject, dittoHeaders),
dittoHeaders));
}
final AuthorizationContext authContext =
AuthorizationModelFactory.newAuthContext(DittoAuthorizationContextType.PRE_AUTHENTICATED_HTTP,
authorizationSubjects);
LOGGER.withCorrelationId(dittoHeaders)
.info("Pre-authentication has been applied resulting in AuthorizationContext <{}>.", authContext);
return CompletableFuture.completedFuture(DefaultAuthenticationResult.successful(dittoHeaders, authContext));
}
private static Optional<String> getPreAuthenticated(final RequestContext requestContext) {
return HttpUtils.getRequestHeader(requestContext, HttpHeader.X_DITTO_PRE_AUTH.getName())
.or(() -> getRequestParam(requestContext, HttpHeader.X_DITTO_PRE_AUTH));
}
private static DittoRuntimeException getAuthenticationFailedException(final DittoHeaders dittoHeaders) {
return GatewayAuthenticationFailedException.newBuilder("No pre-authenticated subject was provided!")
.dittoHeaders(dittoHeaders)
.build();
}
private static List<AuthorizationSubject> getAuthorizationSubjects(final String subjectsCommaSeparated) {
return Arrays.stream(subjectsCommaSeparated.split(","))
.map(String::trim)
.filter(Predicate.not(String::isEmpty))
.map(AuthorizationModelFactory::newAuthSubject)
.toList();
}
private static DittoRuntimeException buildFailedToExtractAuthorizationSubjectsException(
final String preAuthenticatedSubject, final DittoHeaders dittoHeaders) {
final String mPtrn = "Failed to extract AuthorizationSubjects from pre-authenticated header value <{0}>!";
return GatewayAuthenticationFailedException.newBuilder(MessageFormat.format(mPtrn, preAuthenticatedSubject))
.dittoHeaders(dittoHeaders)
.build();
}
@Override
protected AuthenticationResult toFailedAuthenticationResult(final Throwable throwable,
final DittoHeaders dittoHeaders) {
return DefaultAuthenticationResult.failed(dittoHeaders, toDittoRuntimeException(throwable, dittoHeaders));
}
@Override
public AuthorizationContextType getType(final RequestContext requestContext) {
return DittoAuthorizationContextType.PRE_AUTHENTICATED_HTTP;
}
}