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;
44 import org.apache.shiro.authc.BearerToken;
45 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig;
46 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthResponseData;
47 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload;
48 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.AuthHttpServlet;
49 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappedBaseHttpResponse;
50 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappingBaseHttpClient;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 public abstract class AuthService {
57 private static final Logger LOG = LoggerFactory.getLogger(AuthService.class);
58 private final MappingBaseHttpClient httpClient;
59 protected final ObjectMapper mapper;
60 protected final OAuthProviderConfig config;
61 protected final TokenCreator tokenCreator;
62 private final String redirectUri;
64 protected abstract String getTokenVerifierUri();
66 protected abstract Map<String, String> getAdditionalTokenVerifierParams();
68 protected abstract ResponseType getResponseType();
70 protected abstract boolean doSeperateRolesRequest();
72 protected abstract UserTokenPayload mapAccessToken(String spayload)
73 throws JsonMappingException, JsonProcessingException;
75 protected abstract String getLoginUrl(String callbackUrl);
77 protected abstract UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at);
79 protected abstract boolean verifyState(String state);
81 public AuthService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) {
83 this.tokenCreator = tokenCreator;
84 this.redirectUri = redirectUri;
85 this.mapper = new ObjectMapper();
86 this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
87 this.httpClient = new MappingBaseHttpClient(this.config.getUrlOrInternal(), this.config.trustAll());
90 public PublicOAuthProviderConfig getConfig() {
91 return new PublicOAuthProviderConfig(this);
94 protected MappingBaseHttpClient getHttpClient() {
95 return this.httpClient;
98 public void handleRedirect(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException {
99 switch (this.getResponseType()) {
101 this.handleRedirectCode(req, resp, host);
104 sendErrorResponse(resp, "not yet implemented");
111 public void sendLoginRedirectResponse(HttpServletResponse resp, String callbackUrl) {
112 resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
113 resp.setHeader("Location", this.getLoginUrl(callbackUrl));
116 private static void sendErrorResponse(HttpServletResponse resp, String message) throws IOException {
117 resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);
120 private void handleRedirectCode(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException {
121 final String code = req.getParameter("code");
122 final String state = req.getParameter("state");
123 OAuthResponseData response = null;
124 if(this.verifyState(state)) {
125 response = this.getTokenForUser(code, host);
127 if (response != null) {
128 if (this.doSeperateRolesRequest()) {
129 //long expiresAt = this.tokenCreator.getDefaultExp(Math.round(response.getExpires_in()));
130 long expiresAt = this.tokenCreator.getDefaultExp();
131 long issuedAt = this.tokenCreator.getDefaultIat();
132 UserTokenPayload data = this.requestUserRoles(response.getAccess_token(), issuedAt, expiresAt);
134 this.handleUserInfoToken(data, resp, host);
136 sendErrorResponse(resp, "unable to verify user");
139 this.handleUserInfoToken(response.getAccess_token(), resp, host);
142 sendErrorResponse(resp, "unable to verify code");
146 private void handleUserInfoToken(UserTokenPayload data, HttpServletResponse resp, String localHostUrl)
148 BearerToken onapToken = this.tokenCreator.createNewJWT(data);
149 sendTokenResponse(resp, onapToken, localHostUrl);
152 private void handleUserInfoToken(String accessToken, HttpServletResponse resp, String localHostUrl)
155 DecodedJWT jwt = JWT.decode(accessToken);
156 String spayload = base64Decode(jwt.getPayload());
157 LOG.debug("payload in jwt='{}'", spayload);
158 UserTokenPayload data = this.mapAccessToken(spayload);
159 this.handleUserInfoToken(data, resp, localHostUrl);
160 } catch (JWTDecodeException | JsonProcessingException e) {
161 LOG.warn("unable to decode jwt token {}: ", accessToken, e);
162 sendErrorResponse(resp, e.getMessage());
167 protected List<String> mapRoles(List<String> roles) {
168 final Map<String, String> map = this.config.getRoleMapping();
169 return roles.stream().map(r -> map.getOrDefault(r, r)).collect(Collectors.toList());
172 private void sendTokenResponse(HttpServletResponse resp, BearerToken data, String localHostUrl) throws IOException {
173 if (this.redirectUri == null) {
174 byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0];
176 resp.setContentLength(output.length);
177 resp.setContentType("application/json");
178 ServletOutputStream os = null;
179 os = resp.getOutputStream();
182 resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
183 resp.setHeader("Location", assembleUrl(localHostUrl, this.redirectUri, data.getToken()));
189 private static String base64Decode(String data) {
190 return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8);
193 private OAuthResponseData getTokenForUser(String code, String localHostUrl) {
195 Map<String, String> headers = new HashMap<>();
196 headers.put("Content-Type", "application/x-www-form-urlencoded");
197 Map<String, String> params = this.getAdditionalTokenVerifierParams();
198 params.put("code", code);
199 params.put("client_id", this.config.getClientId());
200 params.put("client_secret", this.config.getSecret());
201 params.put("redirect_uri", assembleRedirectUrl(localHostUrl, AuthHttpServlet.REDIRECTURI, this.config.getId()));
202 StringBuilder body = new StringBuilder();
203 for (Entry<String, String> p : params.entrySet()) {
204 body.append(String.format("%s=%s&", p.getKey(), urlEncode(p.getValue())));
207 Optional<MappedBaseHttpResponse<OAuthResponseData>> response =
208 this.httpClient.sendMappedRequest(this.getTokenVerifierUri(), "POST",
209 body.substring(0, body.length() - 1), headers, OAuthResponseData.class);
210 if (response.isPresent() && response.get().isSuccess()) {
211 return response.get().body;
213 LOG.warn("problem get token for code {}", code);
219 * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g.
220 * http://10.20.0.11:8181/oauth/redirect/keycloak
227 public static String assembleRedirectUrl(String host, String baseUri, String serviceId) {
228 return String.format("%s%s/%s", host, baseUri, serviceId);
231 private static String assembleUrl(String host, String uri, String token) {
232 return String.format("%s%s%s", host, uri, token);
235 public static String urlEncode(String s) {
236 return URLEncoder.encode(s, StandardCharsets.UTF_8);
239 public enum ResponseType {
240 CODE, TOKEN, SESSION_STATE
244 public static class PublicOAuthProviderConfig {
247 private String title;
248 private String loginUrl;
250 public String getId() {
254 public void setId(String id) {
258 public String getTitle() {
262 public void setTitle(String title) {
266 public String getLoginUrl() {
270 public void setLoginUrl(String loginUrl) {
271 this.loginUrl = loginUrl;
274 public PublicOAuthProviderConfig(AuthService authService) {
275 this.id = authService.config.getId();
276 this.title = authService.config.getTitle();
277 this.loginUrl = String.format(AuthHttpServlet.LOGIN_REDIRECT_FORMAT, authService.config.getId());