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.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig;
 
  45 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthResponseData;
 
  46 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload;
 
  47 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.AuthHttpServlet;
 
  48 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappedBaseHttpResponse;
 
  49 import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappingBaseHttpClient;
 
  50 import org.opendaylight.aaa.shiro.filters.backport.BearerToken;
 
  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 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.getUrl());
 
  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                 UserTokenPayload data = this.requestUserRoles(response.getAccess_token(), expiresAt);
 
 133                     this.handleUserInfoToken(data, resp, host);
 
 135                     sendErrorResponse(resp, "unable to verify user");
 
 138                 this.handleUserInfoToken(response.getAccess_token(), resp, host);
 
 141             sendErrorResponse(resp, "unable to verify code");
 
 145     private void handleUserInfoToken(UserTokenPayload data, HttpServletResponse resp, String localHostUrl)
 
 147         BearerToken onapToken = this.tokenCreator.createNewJWT(data);
 
 148         sendTokenResponse(resp, onapToken, localHostUrl);
 
 151     private void handleUserInfoToken(String accessToken, HttpServletResponse resp, String localHostUrl)
 
 154             DecodedJWT jwt = JWT.decode(accessToken);
 
 155             String spayload = base64Decode(jwt.getPayload());
 
 156             LOG.debug("payload in jwt='{}'", spayload);
 
 157             UserTokenPayload data = this.mapAccessToken(spayload);
 
 158             this.handleUserInfoToken(data, resp, localHostUrl);
 
 159         } catch (JWTDecodeException | JsonProcessingException e) {
 
 160             LOG.warn("unable to decode jwt token {}: ", accessToken, e);
 
 161             sendErrorResponse(resp, e.getMessage());
 
 166     protected List<String> mapRoles(List<String> roles) {
 
 167         final Map<String, String> map = this.config.getRoleMapping();
 
 168         return roles.stream().map(r -> map.getOrDefault(r, r)).collect(Collectors.toList());
 
 171     private void sendTokenResponse(HttpServletResponse resp, BearerToken data, String localHostUrl) throws IOException {
 
 172         if (this.redirectUri == null) {
 
 173             byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0];
 
 175             resp.setContentLength(output.length);
 
 176             resp.setContentType("application/json");
 
 177             ServletOutputStream os = null;
 
 178             os = resp.getOutputStream();
 
 181             resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
 
 182             resp.setHeader("Location", assembleUrl(localHostUrl, this.redirectUri, data.getToken()));
 
 188     private static String base64Decode(String data) {
 
 189         return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8);
 
 192     private OAuthResponseData getTokenForUser(String code, String localHostUrl) {
 
 194         Map<String, String> headers = new HashMap<>();
 
 195         headers.put("Content-Type", "application/x-www-form-urlencoded");
 
 196         Map<String, String> params = this.getAdditionalTokenVerifierParams();
 
 197         params.put("code", code);
 
 198         params.put("client_id", this.config.getClientId());
 
 199         params.put("client_secret", this.config.getSecret());
 
 200         params.put("redirect_uri", assembleRedirectUrl(localHostUrl, AuthHttpServlet.REDIRECTURI, this.config.getId()));
 
 201         StringBuilder body = new StringBuilder();
 
 202         for (Entry<String, String> p : params.entrySet()) {
 
 203             body.append(String.format("%s=%s&", p.getKey(), urlEncode(p.getValue())));
 
 206         Optional<MappedBaseHttpResponse<OAuthResponseData>> response =
 
 207                 this.httpClient.sendMappedRequest(this.getTokenVerifierUri(), "POST",
 
 208                         body.substring(0, body.length() - 1), headers, OAuthResponseData.class);
 
 209         if (response.isPresent() && response.get().isSuccess()) {
 
 210             return response.get().body;
 
 212         LOG.warn("problem get token for code {}", code);
 
 218      * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g.
 
 219      * http://10.20.0.11:8181/oauth/redirect/keycloak
 
 226     public static String assembleRedirectUrl(String host, String baseUri, String serviceId) {
 
 227         return String.format("%s%s/%s", host, baseUri, serviceId);
 
 230     private static String assembleUrl(String host, String uri, String token) {
 
 231         return String.format("%s%s%s", host, uri, token);
 
 234     public static String urlEncode(String s) {
 
 235         return URLEncoder.encode(s, StandardCharsets.UTF_8);
 
 238     public enum ResponseType {
 
 239         CODE, TOKEN, SESSION_STATE
 
 243     public static class PublicOAuthProviderConfig {
 
 246         private String title;
 
 247         private String loginUrl;
 
 249         public String getId() {
 
 253         public void setId(String id) {
 
 257         public String getTitle() {
 
 261         public void setTitle(String title) {
 
 265         public String getLoginUrl() {
 
 269         public void setLoginUrl(String loginUrl) {
 
 270             this.loginUrl = loginUrl;
 
 273         public PublicOAuthProviderConfig(AuthService authService) {
 
 274             this.id = authService.config.getId();
 
 275             this.title = authService.config.getTitle();
 
 276             this.loginUrl = String.format(AuthHttpServlet.LOGIN_REDIRECT_FORMAT, authService.config.getId());