Inventory TreeView Fixed
[ccsdk/features.git] / sdnr / wt / oauth-provider / provider-jar / src / main / java / org / onap / ccsdk / features / sdnr / wt / oauthprovider / providers / AuthService.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP : ccsdk features
4  * ================================================================================
5  * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property.
6  * All rights reserved.
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
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  *
21  */
22 package org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers;
23
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;
37 import java.util.Map;
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;
44
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;
52
53 public abstract class AuthService {
54
55
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 tokenEndpointRelative;
63     private final String authEndpointAbsolute;
64     private final String logoutEndpointAbsolute;
65
66     private final Map<String, String> logoutTokenMap;
67     protected abstract String getTokenVerifierUri();
68
69     protected abstract Map<String, String> getAdditionalTokenVerifierParams();
70
71     protected abstract ResponseType getResponseType();
72
73     protected abstract boolean doSeperateRolesRequest();
74
75     protected abstract UserTokenPayload mapAccessToken(String spayload)
76             throws JsonMappingException, JsonProcessingException;
77
78     protected abstract String getLoginUrl(String callbackUrl);
79     protected abstract String getLogoutUrl();
80
81     protected abstract UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at);
82
83     protected abstract boolean verifyState(String state);
84
85     public AuthService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) throws UnableToConfigureOAuthService {
86         this.config = config;
87         this.tokenCreator = tokenCreator;
88         this.redirectUri = redirectUri;
89         this.mapper = new ObjectMapper();
90         this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
91         this.httpClient = new MappingBaseHttpClient(this.config.getUrlOrInternal(), this.config.trustAll());
92         this.logoutTokenMap = new HashMap<>();
93         if (this.config.hasToBeConfigured()){
94             Optional<MappedBaseHttpResponse<OpenIdConfigResponseData>> oresponse = this.httpClient.sendMappedRequest(
95                     this.config.getOpenIdConfigUrl(), "GET", null, null, OpenIdConfigResponseData.class);
96             if(oresponse.isEmpty()){
97                 throw new UnableToConfigureOAuthService(this.config.getOpenIdConfigUrl());
98             }
99             MappedBaseHttpResponse<OpenIdConfigResponseData> response = oresponse.get();
100             if(!response.isSuccess()){
101                 throw new UnableToConfigureOAuthService(this.config.getOpenIdConfigUrl(), response.code);
102             }
103             this.tokenEndpointRelative = trimUrl(this.config.getUrlOrInternal(),response.body.getToken_endpoint());
104             this.authEndpointAbsolute = extendUrl(this.config.getUrlOrInternal(),response.body.getAuthorization_endpoint());
105             this.logoutEndpointAbsolute = extendUrl(this.config.getUrlOrInternal(),response.body.getEnd_session_endpoint());
106         }
107         else{
108             this.tokenEndpointRelative = null;
109             this.authEndpointAbsolute = null;
110             this.logoutEndpointAbsolute = null;
111         }
112     }
113
114     public static String trimUrl(String baseUrl, String endpoint) {
115         if(endpoint.startsWith(baseUrl)){
116             return endpoint.substring(baseUrl.length());
117         }
118         if(endpoint.startsWith("http")){
119             return endpoint.substring(endpoint.indexOf("/",8));
120         }
121         return endpoint;
122     }
123     public static String extendUrl(String baseUrl, String endpoint) {
124         if(endpoint.startsWith("http")){
125             endpoint= endpoint.substring(endpoint.indexOf("/",8));
126         }
127         if(baseUrl.endsWith("/")){
128             baseUrl=baseUrl.substring(0,baseUrl.length()-2);
129         }
130         return baseUrl+endpoint;
131     }
132
133     public PublicOAuthProviderConfig getConfig() {
134         return new PublicOAuthProviderConfig(this);
135     }
136
137     protected MappingBaseHttpClient getHttpClient() {
138         return this.httpClient;
139     }
140
141     public void handleRedirect(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException {
142         switch (this.getResponseType()) {
143             case CODE:
144                 this.handleRedirectCode(req, resp, host);
145                 break;
146             case TOKEN:
147                 sendErrorResponse(resp, "not yet implemented");
148                 break;
149             case SESSION_STATE:
150                 break;
151         }
152     }
153
154     public void sendLoginRedirectResponse(HttpServletResponse resp, String callbackUrl) {
155         resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
156         String url = this.authEndpointAbsolute !=null?String.format(
157                 "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s",
158                 this.authEndpointAbsolute, urlEncode(this.config.getClientId()), this.config.getScope(),
159                 urlEncode(callbackUrl)):this.getLoginUrl(callbackUrl);
160         resp.setHeader("Location", url);
161     }
162     public void sendLogoutRedirectResponse(String token, HttpServletResponse resp, String redirectUrl)
163             throws IOException {
164         String idToken = this.logoutTokenMap.getOrDefault(token, null);
165         String logoutEndpoint = this.logoutEndpointAbsolute!=null?this.logoutEndpointAbsolute:this.getLogoutUrl();
166         if(idToken==null) {
167             LOG.debug("unable to find token in map. Do unsafe logout.");
168             resp.sendRedirect(this.logoutEndpointAbsolute);
169             return;
170         }
171         LOG.debug("id token found. redirect to specific logout");
172         resp.sendRedirect(String.format("%s?id_token_hint=%s&post_logout_redirect_uri=%s",logoutEndpoint, idToken,
173                 urlEncode(redirectUrl)));
174     }
175
176
177
178     private static void sendErrorResponse(HttpServletResponse resp, String message) throws IOException {
179         resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);
180     }
181
182     private void handleRedirectCode(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException {
183         final String code = req.getParameter("code");
184         final String state = req.getParameter("state");
185         OAuthResponseData response = null;
186         if(this.verifyState(state)) {
187             response = this.getTokenForUser(code, host);
188         }
189         if (response != null) {
190             if (this.doSeperateRolesRequest()) {
191                 LOG.debug("do a seperate role request");
192                 long expiresAt = this.tokenCreator.getDefaultExp();
193                 long issuedAt = this.tokenCreator.getDefaultIat();
194                 UserTokenPayload data = this.requestUserRoles(response.getAccess_token(), issuedAt, expiresAt);
195                 if (data != null) {
196                     BearerToken createdToken = this.handleUserInfoToken(data, resp, host);
197                     this.logoutTokenMap.put(createdToken.getToken(),response.getId_token());
198                 } else {
199                     sendErrorResponse(resp, "unable to verify user");
200                 }
201             } else {
202                 BearerToken createdToken = this.handleUserInfoToken(response.getAccess_token(), resp, host);
203                 this.logoutTokenMap.put(createdToken.getToken(),response.getId_token());
204             }
205         } else {
206             sendErrorResponse(resp, "unable to verify code");
207         }
208     }
209
210     private BearerToken handleUserInfoToken(UserTokenPayload data, HttpServletResponse resp, String localHostUrl)
211             throws IOException {
212         BearerToken onapToken = this.tokenCreator.createNewJWT(data);
213         sendTokenResponse(resp, onapToken, localHostUrl);
214         return onapToken;
215     }
216
217     private BearerToken handleUserInfoToken(String accessToken, HttpServletResponse resp, String localHostUrl)
218             throws IOException {
219         try {
220             DecodedJWT jwt = JWT.decode(accessToken);
221             String spayload = base64Decode(jwt.getPayload());
222             LOG.debug("payload in jwt='{}'", spayload);
223             UserTokenPayload data = this.mapAccessToken(spayload);
224             return this.handleUserInfoToken(data, resp, localHostUrl);
225         } catch (JWTDecodeException | JsonProcessingException e) {
226             LOG.warn("unable to decode jwt token {}: ", accessToken, e);
227             sendErrorResponse(resp, e.getMessage());
228         }
229         return null;
230     }
231
232
233     protected List<String> mapRoles(List<String> roles) {
234         final Map<String, String> map = this.config.getRoleMapping();
235         return roles.stream().map(r -> map.getOrDefault(r, r)).collect(Collectors.toList());
236     }
237
238     private void sendTokenResponse(HttpServletResponse resp, BearerToken data, String localHostUrl) throws IOException {
239         if (this.redirectUri == null) {
240             byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0];
241             resp.setStatus(200);
242             resp.setContentLength(output.length);
243             resp.setContentType("application/json");
244             resp.addCookie(this.tokenCreator.createAuthCookie(data));
245             ServletOutputStream os = null;
246             os = resp.getOutputStream();
247             os.write(output);
248         } else {
249             resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
250             resp.setHeader("Location", assembleUrl(localHostUrl, this.redirectUri, data.getToken()));
251             resp.addCookie(this.tokenCreator.createAuthCookie(data));
252         }
253     }
254
255
256
257     private static String base64Decode(String data) {
258         return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8);
259     }
260
261     private OAuthResponseData getTokenForUser(String code, String localHostUrl) {
262
263         Map<String, String> headers = new HashMap<>();
264         headers.put("Content-Type", "application/x-www-form-urlencoded");
265         headers.put("Accept", "application/json");
266         Map<String, String> params = this.getAdditionalTokenVerifierParams();
267         params.put("code", code);
268         params.put("client_id", this.config.getClientId());
269         params.put("client_secret", this.config.getSecret());
270         params.put("redirect_uri", assembleRedirectUrl(localHostUrl, AuthHttpServlet.REDIRECTURI, this.config.getId()));
271         StringBuilder body = new StringBuilder();
272         for (Entry<String, String> p : params.entrySet()) {
273             body.append(String.format("%s=%s&", p.getKey(), urlEncode(p.getValue())));
274         }
275
276         String url = this.tokenEndpointRelative !=null?this.tokenEndpointRelative :this.getTokenVerifierUri();
277         Optional<MappedBaseHttpResponse<OAuthResponseData>> response =
278                 this.httpClient.sendMappedRequest(url, "POST",
279                         body.substring(0, body.length() - 1), headers, OAuthResponseData.class);
280         if (response.isPresent() && response.get().isSuccess()) {
281             return response.get().body;
282         }
283         LOG.warn("problem get token for code {}", code);
284
285         return null;
286     }
287
288     /**
289      * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g.
290      * http://10.20.0.11:8181/oauth/redirect/keycloak
291      *
292      * @param host
293      * @param baseUri
294      * @param serviceId
295      * @return
296      */
297     public static String assembleRedirectUrl(String host, String baseUri, String serviceId) {
298         return String.format("%s%s/%s", host, baseUri, serviceId);
299     }
300
301     private static String assembleUrl(String host, String uri, String token) {
302         return String.format("%s%s%s", host, uri, token);
303     }
304
305     public static String urlEncode(String s) {
306         return URLEncoder.encode(s, StandardCharsets.UTF_8);
307     }
308
309
310
311     public enum ResponseType {
312         CODE, TOKEN, SESSION_STATE
313     }
314
315
316     public static class PublicOAuthProviderConfig {
317
318         private String id;
319         private String title;
320         private String loginUrl;
321
322         public String getId() {
323             return id;
324         }
325
326         public void setId(String id) {
327             this.id = id;
328         }
329
330         public String getTitle() {
331             return title;
332         }
333
334         public void setTitle(String title) {
335             this.title = title;
336         }
337
338         public String getLoginUrl() {
339             return loginUrl;
340         }
341
342         public void setLoginUrl(String loginUrl) {
343             this.loginUrl = loginUrl;
344         }
345
346         public PublicOAuthProviderConfig(AuthService authService) {
347             this.id = authService.config.getId();
348             this.title = authService.config.getTitle();
349             this.loginUrl = String.format(AuthHttpServlet.LOGIN_REDIRECT_FORMAT, authService.config.getId());
350         }
351
352     }
353
354
355
356 }