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;
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;
52 public abstract class AuthService {
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;
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 public AuthService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) {
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());
87 public PublicOAuthProviderConfig getConfig(String host) {
88 return new PublicOAuthProviderConfig(this, host);
91 public void handleRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException {
92 switch (this.getResponseType()) {
94 this.handleRedirectCode(req, resp);
97 sendErrorResponse(resp, "not yet implemented");
105 private static void sendErrorResponse(HttpServletResponse resp, String message) throws IOException {
106 resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);
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()) {
117 this.handleUserInfoToken(response.getAccess_token(), resp);
120 sendErrorResponse(resp, "unable to verify code");
125 private void handleUserInfoToken(String accessToken, HttpServletResponse resp) throws IOException {
127 DecodedJWT jwt = JWT.decode(accessToken);
129 String spayload = base64Decode(jwt.getPayload());
130 LOG.debug("payload in jwt from keycload='{}'", spayload);
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());
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];
146 resp.setContentLength(output.length);
147 resp.setContentType("application/json");
148 ServletOutputStream os = null;
149 os = resp.getOutputStream();
152 resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
153 resp.setHeader("Location", assembleUrl(this.localHostUrl, this.redirectUri, data.getToken()));
159 private static String base64Decode(String data) {
160 return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8);
163 private OAuthResponseData getTokenForUser(String code) {
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())));
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;
183 LOG.warn("problem get token for code {}", code);
191 * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g.
192 * http://10.20.0.11:8181/oauth/redirect/keycloak
199 public static String assembleRedirectUrl(String host, String baseUri, String serviceId) {
200 return String.format("%s%s/%s", host, baseUri, serviceId);
203 private static String assembleUrl(String host, String uri, String token) {
204 return String.format("%s%s%s", host, uri, token);
207 public static String urlEncode(String s) {
208 return URLEncoder.encode(s, StandardCharsets.UTF_8);
211 public enum ResponseType {
212 CODE, TOKEN, SESSION_STATE
216 public static class PublicOAuthProviderConfig {
219 private String title;
220 private String loginUrl;
222 public String getId() {
226 public void setId(String id) {
230 public String getTitle() {
234 public void setTitle(String title) {
238 public String getLoginUrl() {
242 public void setLoginUrl(String loginUrl) {
243 this.loginUrl = loginUrl;
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));