2 * ============LICENSE_START=======================================================
3 * ONAP : ccsdk features
4 * ================================================================================
5 * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers;
24 import com.auth0.jwt.JWT;
25 import com.auth0.jwt.exceptions.JWTDecodeException;
26 import com.auth0.jwt.interfaces.DecodedJWT;
27 import com.fasterxml.jackson.core.JsonProcessingException;
28 import com.fasterxml.jackson.databind.DeserializationFeature;
29 import com.fasterxml.jackson.databind.JsonMappingException;
30 import com.fasterxml.jackson.databind.ObjectMapper;
31 import java.io.IOException;
32 import java.net.URLEncoder;
33 import java.nio.charset.StandardCharsets;
34 import java.util.Base64;
35 import java.util.HashMap;
36 import java.util.List;
38 import java.util.Map.Entry;
39 import java.util.Optional;
40 import java.util.stream.Collectors;
41 import javax.servlet.ServletOutputStream;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpServletResponse;
45 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.*;
46 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.AuthHttpServlet;
47 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappedBaseHttpResponse;
48 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappingBaseHttpClient;
49 import org.apache.shiro.authc.BearerToken;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
53 public abstract class AuthService {
56 private static final Logger LOG = LoggerFactory.getLogger(AuthService.class);
57 private final MappingBaseHttpClient httpClient;
58 protected final ObjectMapper mapper;
59 protected final OAuthProviderConfig config;
60 protected final TokenCreator tokenCreator;
61 private final String redirectUri;
62 private final String tokenEndpoint;
63 private final String authEndpoint;
65 protected abstract String getTokenVerifierUri();
67 protected abstract Map<String, String> getAdditionalTokenVerifierParams();
69 protected abstract ResponseType getResponseType();
71 protected abstract boolean doSeperateRolesRequest();
73 protected abstract UserTokenPayload mapAccessToken(String spayload)
74 throws JsonMappingException, JsonProcessingException;
76 protected abstract String getLoginUrl(String callbackUrl);
78 protected abstract UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at);
80 protected abstract boolean verifyState(String state);
82 public AuthService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) throws UnableToConfigureOAuthService {
84 this.tokenCreator = tokenCreator;
85 this.redirectUri = redirectUri;
86 this.mapper = new ObjectMapper();
87 this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
88 this.httpClient = new MappingBaseHttpClient(this.config.getUrlOrInternal(), this.config.trustAll());
89 if (this.config.hasToBeConfigured()){
90 Optional<MappedBaseHttpResponse<OpenIdConfigResponseData>> oresponse = this.httpClient.sendMappedRequest(
91 this.config.getOpenIdConfigUrl(), "GET", null, null, OpenIdConfigResponseData.class);
92 if(oresponse.isEmpty()){
93 throw new UnableToConfigureOAuthService(this.config.getOpenIdConfigUrl());
95 MappedBaseHttpResponse<OpenIdConfigResponseData> response = oresponse.get();
96 if(!response.isSuccess()){
97 throw new UnableToConfigureOAuthService(this.config.getOpenIdConfigUrl(), response.code);
99 this.tokenEndpoint = response.body.getToken_endpoint();
100 this.authEndpoint = response.body.getAuthorization_endpoint();
103 this.tokenEndpoint = null;
104 this.authEndpoint = null;
108 public PublicOAuthProviderConfig getConfig() {
109 return new PublicOAuthProviderConfig(this);
112 protected MappingBaseHttpClient getHttpClient() {
113 return this.httpClient;
116 public void handleRedirect(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException {
117 switch (this.getResponseType()) {
119 this.handleRedirectCode(req, resp, host);
122 sendErrorResponse(resp, "not yet implemented");
129 public void sendLoginRedirectResponse(HttpServletResponse resp, String callbackUrl) {
130 resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
131 String url = this.authEndpoint!=null?String.format(
132 "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s",
133 this.authEndpoint, urlEncode(this.config.getClientId()), this.config.getScope(),
134 urlEncode(callbackUrl)):this.getLoginUrl(callbackUrl);
135 resp.setHeader("Location", url);
138 private static void sendErrorResponse(HttpServletResponse resp, String message) throws IOException {
139 resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);
142 private void handleRedirectCode(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException {
143 final String code = req.getParameter("code");
144 final String state = req.getParameter("state");
145 OAuthResponseData response = null;
146 if(this.verifyState(state)) {
147 response = this.getTokenForUser(code, host);
149 if (response != null) {
150 if (this.doSeperateRolesRequest()) {
151 //long expiresAt = this.tokenCreator.getDefaultExp(Math.round(response.getExpires_in()));
152 long expiresAt = this.tokenCreator.getDefaultExp();
153 long issuedAt = this.tokenCreator.getDefaultIat();
154 UserTokenPayload data = this.requestUserRoles(response.getAccess_token(), issuedAt, expiresAt);
156 this.handleUserInfoToken(data, resp, host);
158 sendErrorResponse(resp, "unable to verify user");
161 this.handleUserInfoToken(response.getAccess_token(), resp, host);
164 sendErrorResponse(resp, "unable to verify code");
168 private void handleUserInfoToken(UserTokenPayload data, HttpServletResponse resp, String localHostUrl)
170 BearerToken onapToken = this.tokenCreator.createNewJWT(data);
171 sendTokenResponse(resp, onapToken, localHostUrl);
174 private void handleUserInfoToken(String accessToken, HttpServletResponse resp, String localHostUrl)
177 DecodedJWT jwt = JWT.decode(accessToken);
178 String spayload = base64Decode(jwt.getPayload());
179 LOG.debug("payload in jwt='{}'", spayload);
180 UserTokenPayload data = this.mapAccessToken(spayload);
181 this.handleUserInfoToken(data, resp, localHostUrl);
182 } catch (JWTDecodeException | JsonProcessingException e) {
183 LOG.warn("unable to decode jwt token {}: ", accessToken, e);
184 sendErrorResponse(resp, e.getMessage());
189 protected List<String> mapRoles(List<String> roles) {
190 final Map<String, String> map = this.config.getRoleMapping();
191 return roles.stream().map(r -> map.getOrDefault(r, r)).collect(Collectors.toList());
194 private void sendTokenResponse(HttpServletResponse resp, BearerToken data, String localHostUrl) throws IOException {
195 if (this.redirectUri == null) {
196 byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0];
198 resp.setContentLength(output.length);
199 resp.setContentType("application/json");
200 ServletOutputStream os = null;
201 os = resp.getOutputStream();
204 resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
205 resp.setHeader("Location", assembleUrl(localHostUrl, this.redirectUri, data.getToken()));
211 private static String base64Decode(String data) {
212 return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8);
215 private OAuthResponseData getTokenForUser(String code, String localHostUrl) {
217 Map<String, String> headers = new HashMap<>();
218 headers.put("Content-Type", "application/x-www-form-urlencoded");
219 Map<String, String> params = this.getAdditionalTokenVerifierParams();
220 params.put("code", code);
221 params.put("client_id", this.config.getClientId());
222 params.put("client_secret", this.config.getSecret());
223 params.put("redirect_uri", assembleRedirectUrl(localHostUrl, AuthHttpServlet.REDIRECTURI, this.config.getId()));
224 StringBuilder body = new StringBuilder();
225 for (Entry<String, String> p : params.entrySet()) {
226 body.append(String.format("%s=%s&", p.getKey(), urlEncode(p.getValue())));
229 String url = this.tokenEndpoint!=null?this.tokenEndpoint:this.getTokenVerifierUri();
230 Optional<MappedBaseHttpResponse<OAuthResponseData>> response =
231 this.httpClient.sendMappedRequest(url, "POST",
232 body.substring(0, body.length() - 1), headers, OAuthResponseData.class);
233 if (response.isPresent() && response.get().isSuccess()) {
234 return response.get().body;
236 LOG.warn("problem get token for code {}", code);
242 * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g.
243 * http://10.20.0.11:8181/oauth/redirect/keycloak
250 public static String assembleRedirectUrl(String host, String baseUri, String serviceId) {
251 return String.format("%s%s/%s", host, baseUri, serviceId);
254 private static String assembleUrl(String host, String uri, String token) {
255 return String.format("%s%s%s", host, uri, token);
258 public static String urlEncode(String s) {
259 return URLEncoder.encode(s, StandardCharsets.UTF_8);
262 public enum ResponseType {
263 CODE, TOKEN, SESSION_STATE
267 public static class PublicOAuthProviderConfig {
270 private String title;
271 private String loginUrl;
273 public String getId() {
277 public void setId(String id) {
281 public String getTitle() {
285 public void setTitle(String title) {
289 public String getLoginUrl() {
293 public void setLoginUrl(String loginUrl) {
294 this.loginUrl = loginUrl;
297 public PublicOAuthProviderConfig(AuthService authService) {
298 this.id = authService.config.getId();
299 this.title = authService.config.getTitle();
300 this.loginUrl = String.format(AuthHttpServlet.LOGIN_REDIRECT_FORMAT, authService.config.getId());