001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.ws.client;
015
016import org.gbif.ws.WebApplicationException;
017import org.gbif.ws.security.Md5EncodeService;
018import org.gbif.ws.security.PrivateKeyNotFoundException;
019import org.gbif.ws.security.RequestDataToSign;
020import org.gbif.ws.security.SigningService;
021
022import java.util.Collection;
023import java.util.Map;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027import org.springframework.http.HttpHeaders;
028import org.springframework.http.HttpStatus;
029
030import feign.RequestInterceptor;
031import feign.RequestTemplate;
032
033import static org.gbif.ws.client.ClientUtils.isPostOrPutRequest;
034import static org.gbif.ws.client.ClientUtils.isRequestBodyNotEmpty;
035
036/**
037 * An authentication request interceptor for trusted GBIF applications.
038 * It requires an application key and can then act on behalf of any username.
039 *
040 * Request interceptor adding an HTTP Authentication header to the HTTP request using the custom GBIF schema
041 * for trusted applications.
042 * In addition to the Authentication this request interceptor will add these headers to the request:
043 * <ul>
044 * <li>Content-MD5: the MD5 hash for the request body</li>
045 * <li>x-gbif-user: the username of the proxied user</li>
046 * </ul>
047 *
048 * Note that the users role still depends on the roles defined in the user account and will not default
049 * to ADMIN. The user also has to exist for the webservice authorization to work.
050 */
051public class GbifAuthRequestInterceptor implements RequestInterceptor {
052
053  private static final Logger LOG = LoggerFactory.getLogger(GbifAuthRequestInterceptor.class);
054
055  private SigningService signingService;
056  private Md5EncodeService md5EncodeService;
057  private String username;
058  private String appKey;
059  private String secretKey;
060
061  public GbifAuthRequestInterceptor(
062      String username,
063      String appKey,
064      String secretKey,
065      SigningService signingService,
066      Md5EncodeService md5EncodeService) {
067    this.signingService = signingService;
068    this.md5EncodeService = md5EncodeService;
069    this.username = username;
070    this.appKey = appKey;
071    this.secretKey = secretKey;
072  }
073
074  @Override
075  public void apply(RequestTemplate template) {
076    RequestDataToSign requestDataToSign = new RequestDataToSign();
077    requestDataToSign.setMethod(template.method());
078    requestDataToSign.setUrl(removeQueryParameters(template.url()));
079    requestDataToSign.setUser(username);
080
081    if (isPostOrPutRequest(template) && isRequestBodyNotEmpty(template)) {
082      Map<String, Collection<String>> headers = template.headers();
083
084      Collection<String> contentTypeHeaders = headers.get(HttpHeaders.CONTENT_TYPE);
085
086      String contentType =
087          (contentTypeHeaders != null && !contentTypeHeaders.isEmpty())
088              ? contentTypeHeaders.iterator().next()
089              : "application/json";
090      requestDataToSign.setContentType(contentType);
091
092      String contentMd5 = md5EncodeService.encode(template.body());
093      requestDataToSign.setContentTypeMd5(contentMd5);
094
095      template.header("Content-MD5", contentMd5);
096    }
097
098    LOG.debug("Client data to sign: {}", requestDataToSign.stringToSign());
099
100    try {
101      String signature = signingService.buildSignature(requestDataToSign, secretKey);
102
103      template.header("x-gbif-user", username);
104      template.header("Authorization", "GBIF " + appKey + ":" + signature);
105    } catch (PrivateKeyNotFoundException e) {
106      LOG.debug("Private key was not found for the application {}", appKey);
107      throw new WebApplicationException(
108          "Private key was not found for the application " + appKey, HttpStatus.UNAUTHORIZED);
109    }
110  }
111
112  /**
113   * Remove query parameters from the URL.
114   */
115  private String removeQueryParameters(String url) {
116    return url.split("\\?")[0];
117  }
118}