-
Notifications
You must be signed in to change notification settings - Fork 214
/
DevOpsOAuth2AuthenticationDirective.java
executable file
·151 lines (130 loc) · 7.24 KB
/
DevOpsOAuth2AuthenticationDirective.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
/*
* 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.gateway.service.endpoints.directives.auth;
import static org.apache.pekko.http.javadsl.server.Directives.extractRequestContext;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.gateway.api.GatewayAuthenticationFailedException;
import org.eclipse.ditto.gateway.service.security.authentication.AuthenticationResult;
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationProvider;
import org.eclipse.ditto.gateway.service.util.config.security.DevOpsConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.pekko.http.javadsl.model.HttpHeader;
import org.apache.pekko.http.javadsl.server.AuthorizationFailedRejection;
import org.apache.pekko.http.javadsl.server.Directives;
import org.apache.pekko.http.javadsl.server.RequestContext;
import org.apache.pekko.http.javadsl.server.Route;
import scala.util.Try;
/**
* Custom Pekko Http directive performing oauth2 with an {@link #expectedSubjects expected subject}.
*/
public final class DevOpsOAuth2AuthenticationDirective implements DevopsAuthenticationDirective {
private static final Logger LOGGER = LoggerFactory.getLogger(DevOpsOAuth2AuthenticationDirective.class);
/**
* The Http basic auth realm for the "ditto-devops" user used for /devops resource.
*/
public static final String REALM_DEVOPS = "DITTO-DEVOPS";
/**
* The Http basic auth realm for the "ditto-devops" user used for /status resource.
*/
public static final String REALM_STATUS = "DITTO-STATUS";
private final JwtAuthenticationProvider jwtAuthenticationProvider;
private final Collection<String> expectedSubjects;
private DevOpsOAuth2AuthenticationDirective(final JwtAuthenticationProvider jwtAuthenticationProvider,
final Collection<String> expectedSubjects) {
this.jwtAuthenticationProvider = checkNotNull(jwtAuthenticationProvider, "jwtAuthenticationProvider");
this.expectedSubjects = expectedSubjects;
}
/**
* Returns an instance of {@code DevOpsAuthenticationDirective}.
*
* @param devOpsConfig the configuration settings of the Gateway service's DevOps endpoint.
* @param jwtAuthenticationProvider the authentication provider OAuth2 authentication at status resources.
* @return the instance.
* @throws NullPointerException if {@code devOpsConfig} is {@code null}.
*/
public static DevOpsOAuth2AuthenticationDirective status(final DevOpsConfig devOpsConfig,
final JwtAuthenticationProvider jwtAuthenticationProvider) {
final Collection<String> expectedSubjects = devOpsConfig.getStatusOAuth2Subjects();
return new DevOpsOAuth2AuthenticationDirective(jwtAuthenticationProvider, expectedSubjects);
}
/**
* Returns an instance of {@code DevOpsAuthenticationDirective}.
*
* @param devOpsConfig the configuration settings of the Gateway service's DevOps endpoint.
* @param jwtAuthenticationProvider the authentication provider OAuth2 authentication at devops resources.
* @return the instance.
* @throws NullPointerException if {@code devOpsConfig} is {@code null}.
*/
public static DevOpsOAuth2AuthenticationDirective devops(final DevOpsConfig devOpsConfig,
final JwtAuthenticationProvider jwtAuthenticationProvider) {
final Collection<String> expectedSubjects = devOpsConfig.getDevopsOAuth2Subjects();
return new DevOpsOAuth2AuthenticationDirective(jwtAuthenticationProvider, expectedSubjects);
}
/**
* Authenticates the devops resources with the chosen authentication method.
*
* @param realm the realm to apply.
* @param inner the inner route, which will be performed on successful authentication.
* @return the inner route wrapped with authentication.
*/
public Route authenticateDevOps(final String realm, final Route inner) {
LOGGER.debug("DevOps OAuth authentication is enabled for {}.", realm);
return extractRequestContext(requestContext -> {
final String authorizationHeaderValue = requestContext.getRequest()
.getHeader("authorization")
.map(HttpHeader::value)
.orElse("");
LOGGER.debug("Trying to use OAuth2 authentication for authorization header <{}>", authorizationHeaderValue);
final CompletionStage<AuthenticationResult> authenticationResult =
jwtAuthenticationProvider.authenticate(requestContext, DittoHeaders.empty());
final Function<Try<AuthenticationResult>, Route> handleAuthenticationTry =
authenticationResultTry -> handleAuthenticationTry(authenticationResultTry, inner, requestContext);
return Directives.onComplete(authenticationResult, handleAuthenticationTry);
});
}
private Route handleAuthenticationTry(final Try<AuthenticationResult> authenticationResultTry, final Route inner,
final RequestContext requestContext) {
if (authenticationResultTry.isSuccess()) {
final AuthenticationResult authenticationResult = authenticationResultTry.get();
if (!authenticationResult.isSuccess()) {
LOGGER.warn("DevOps Oauth authentication was not successful for request: '{}' because of '{}'.",
requestContext.getRequest(), authenticationResult.getReasonOfFailure().getMessage());
return Directives.failWith(authenticationResult.getReasonOfFailure());
} else {
final List<String> authorizationSubjectIds =
authenticationResult.getAuthorizationContext().getAuthorizationSubjectIds();
final boolean isAuthorized = expectedSubjects.isEmpty() || authorizationSubjectIds.stream().anyMatch(expectedSubjects::contains);
if (isAuthorized) {
LOGGER.info("DevOps Oauth authentication was successful.");
return inner;
} else {
final String message = String.format(
"Unauthorized subject(s): <%s>. Expected: <%s>",
authorizationSubjectIds, expectedSubjects
);
final GatewayAuthenticationFailedException reasonOfFailure =
GatewayAuthenticationFailedException.fromMessage(message, DittoHeaders.empty());
LOGGER.warn("DevOps Oauth authentication failed.", reasonOfFailure);
return Directives.failWith(reasonOfFailure);
}
}
}
return Directives.reject(AuthorizationFailedRejection.get());
}
}