ec685a00345f724aa84a5ed9a0605f7dc3416974
[ccsdk/features.git] /
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.Map;
37 import java.util.Map.Entry;
38 import java.util.Optional;
39 import javax.servlet.ServletOutputStream;
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpServletResponse;
42 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig;
43 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthResponseData;
44 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload;
45 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.AuthHttpServlet;
46 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappedBaseHttpResponse;
47 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappingBaseHttpClient;
48 import org.opendaylight.aaa.shiro.filters.backport.BearerToken;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public abstract class AuthService {
53
54
55     private static final Logger LOG = LoggerFactory.getLogger(AuthService.class.getName());
56     private final MappingBaseHttpClient httpClient;
57     protected final ObjectMapper mapper;
58     protected final OAuthProviderConfig config;
59     protected final TokenCreator tokenCreator;
60     private final String redirectUri;
61     private String localHostUrl;
62     public void setLocalHostUrl(String url) {
63         this.localHostUrl = url;
64     }
65     protected abstract String getTokenVerifierUri();
66
67     protected abstract Map<String, String> getAdditionalTokenVerifierParams();
68
69     protected abstract ResponseType getResponseType();
70
71     protected abstract boolean doSeperateRolesRequest();
72
73     protected abstract UserTokenPayload mapAccessToken(String spayload)
74             throws JsonMappingException, JsonProcessingException;
75
76     protected abstract String getLoginUrl(String callbackUrl);
77
78     public AuthService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) {
79         this.config = config;
80         this.tokenCreator = tokenCreator;
81         this.redirectUri = redirectUri;
82         this.mapper = new ObjectMapper();
83         this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
84         this.httpClient = new MappingBaseHttpClient(this.config.getHost());
85     }
86
87     public PublicOAuthProviderConfig getConfig(String host) {
88         return new PublicOAuthProviderConfig(this, host);
89     }
90
91     public void handleRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException {
92         switch (this.getResponseType()) {
93             case CODE:
94                 this.handleRedirectCode(req, resp);
95                 break;
96             case TOKEN:
97                 sendErrorResponse(resp, "not yet implemented");
98                 break;
99             case SESSION_STATE:
100                 break;
101         }
102
103     }
104
105     private static void sendErrorResponse(HttpServletResponse resp, String message) throws IOException {
106         resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);
107
108     }
109
110     private void handleRedirectCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
111         final String code = req.getParameter("code");
112         OAuthResponseData response = this.getTokenForUser(code);
113         if (response != null) {
114             if (this.doSeperateRolesRequest()) {
115
116             } else {
117                 this.handleUserInfoToken(response.getAccess_token(), resp);
118             }
119         } else {
120             sendErrorResponse(resp, "unable to verify code");
121         }
122
123     }
124
125     private void handleUserInfoToken(String accessToken, HttpServletResponse resp) throws IOException {
126         try {
127             DecodedJWT jwt = JWT.decode(accessToken);
128
129             String spayload = base64Decode(jwt.getPayload());
130             LOG.debug("payload in jwt from keycload='{}'", spayload);
131
132             UserTokenPayload data = this.mapAccessToken(spayload);
133             BearerToken onapToken = this.tokenCreator.createNewJWT(data);
134             sendTokenResponse(resp, onapToken);
135         } catch (JWTDecodeException | JsonProcessingException e) {
136             LOG.warn("unable to decode jwt token {}: ", accessToken, e);
137             sendErrorResponse(resp, e.getMessage());
138         }
139     }
140
141
142     private void sendTokenResponse(HttpServletResponse resp, BearerToken data) throws IOException {
143         if (this.redirectUri == null) {
144             byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0];
145             resp.setStatus(200);
146             resp.setContentLength(output.length);
147             resp.setContentType("application/json");
148             ServletOutputStream os = null;
149             os = resp.getOutputStream();
150             os.write(output);
151         } else {
152             resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
153             resp.setHeader("Location", assembleUrl(this.localHostUrl, this.redirectUri, data.getToken()));
154         }
155     }
156
157
158
159     private static String base64Decode(String data) {
160         return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8);
161     }
162
163     private OAuthResponseData getTokenForUser(String code) {
164
165         Map<String, String> headers = new HashMap<>();
166         headers.put("Content-Type", "application/x-www-form-urlencoded");
167         Map<String, String> params = this.getAdditionalTokenVerifierParams();
168         params.put("code", code);
169         params.put("client_id", this.config.getClientId());
170         params.put("client_secret", this.config.getSecret());
171         params.put("redirect_uri",
172                 assembleRedirectUrl(localHostUrl, AuthHttpServlet.REDIRECTURI, this.config.getId()));
173         StringBuilder body = new StringBuilder();
174         for (Entry<String, String> p : params.entrySet()) {
175             body.append(String.format("%s=%s&", p.getKey(), urlEncode(p.getValue())));
176         }
177
178         Optional<MappedBaseHttpResponse<OAuthResponseData>> response = this.httpClient.sendMappedRequest(this.getTokenVerifierUri(),
179                 "POST", body.substring(0, body.length() - 1), headers, OAuthResponseData.class);
180         if (response.isPresent() && response.get().isSuccess()) {
181             return response.get().body;
182         }
183         LOG.warn("problem get token for code {}", code);
184
185         return null;
186     }
187
188
189
190     /**
191      * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g.
192      * http://10.20.0.11:8181/oauth/redirect/keycloak
193      *
194      * @param host
195      * @param baseUri
196      * @param serviceId
197      * @return
198      */
199     public static String assembleRedirectUrl(String host, String baseUri, String serviceId) {
200         return String.format("%s%s/%s", host, baseUri, serviceId);
201     }
202
203     private static String assembleUrl(String host, String uri, String token) {
204         return String.format("%s%s%s", host, uri, token);
205     }
206
207     public static String urlEncode(String s) {
208         return URLEncoder.encode(s, StandardCharsets.UTF_8);
209     }
210
211     public enum ResponseType {
212         CODE, TOKEN, SESSION_STATE
213     }
214
215
216     public static class PublicOAuthProviderConfig {
217
218         private String id;
219         private String title;
220         private String loginUrl;
221
222         public String getId() {
223             return id;
224         }
225
226         public void setId(String id) {
227             this.id = id;
228         }
229
230         public String getTitle() {
231             return title;
232         }
233
234         public void setTitle(String title) {
235             this.title = title;
236         }
237
238         public String getLoginUrl() {
239             return loginUrl;
240         }
241
242         public void setLoginUrl(String loginUrl) {
243             this.loginUrl = loginUrl;
244         }
245
246         public PublicOAuthProviderConfig(AuthService authService, String host) {
247             this.id = authService.config.getId();
248             this.title = authService.config.getTitle();
249             this.loginUrl = authService.getLoginUrl(
250                     assembleRedirectUrl(host, AuthHttpServlet.REDIRECTURI, this.id));
251         }
252
253     }
254
255
256
257 }