View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    *     http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  package org.gbif.ws.client;
15  
16  import org.gbif.ws.WebApplicationException;
17  import org.gbif.ws.security.Md5EncodeService;
18  import org.gbif.ws.security.PrivateKeyNotFoundException;
19  import org.gbif.ws.security.RequestDataToSign;
20  import org.gbif.ws.security.SigningService;
21  
22  import java.util.Collection;
23  import java.util.Map;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  import org.springframework.http.HttpHeaders;
28  import org.springframework.http.HttpStatus;
29  
30  import feign.RequestInterceptor;
31  import feign.RequestTemplate;
32  
33  import static org.gbif.ws.client.ClientUtils.isPostOrPutRequest;
34  import static org.gbif.ws.client.ClientUtils.isRequestBodyNotEmpty;
35  
36  /**
37   * An authentication request interceptor for trusted GBIF applications.
38   * It requires an application key and can then act on behalf of any username.
39   *
40   * Request interceptor adding an HTTP Authentication header to the HTTP request using the custom GBIF schema
41   * for trusted applications.
42   * In addition to the Authentication this request interceptor will add these headers to the request:
43   * <ul>
44   * <li>Content-MD5: the MD5 hash for the request body</li>
45   * <li>x-gbif-user: the username of the proxied user</li>
46   * </ul>
47   *
48   * Note that the users role still depends on the roles defined in the user account and will not default
49   * to ADMIN. The user also has to exist for the webservice authorization to work.
50   */
51  public class GbifAuthRequestInterceptor implements RequestInterceptor {
52  
53    private static final Logger LOG = LoggerFactory.getLogger(GbifAuthRequestInterceptor.class);
54  
55    private SigningService signingService;
56    private Md5EncodeService md5EncodeService;
57    private String username;
58    private String appKey;
59    private String secretKey;
60  
61    public GbifAuthRequestInterceptor(
62        String username,
63        String appKey,
64        String secretKey,
65        SigningService signingService,
66        Md5EncodeService md5EncodeService) {
67      this.signingService = signingService;
68      this.md5EncodeService = md5EncodeService;
69      this.username = username;
70      this.appKey = appKey;
71      this.secretKey = secretKey;
72    }
73  
74    @Override
75    public void apply(RequestTemplate template) {
76      RequestDataToSigntDataToSign">RequestDataToSign requestDataToSign = new RequestDataToSign();
77      requestDataToSign.setMethod(template.method());
78      requestDataToSign.setUrl(removeQueryParameters(template.url()));
79      requestDataToSign.setUser(username);
80  
81      if (isPostOrPutRequest(template) && isRequestBodyNotEmpty(template)) {
82        Map<String, Collection<String>> headers = template.headers();
83  
84        Collection<String> contentTypeHeaders = headers.get(HttpHeaders.CONTENT_TYPE);
85  
86        String contentType =
87            (contentTypeHeaders != null && !contentTypeHeaders.isEmpty())
88                ? contentTypeHeaders.iterator().next()
89                : "application/json";
90        requestDataToSign.setContentType(contentType);
91  
92        String contentMd5 = md5EncodeService.encode(template.body());
93        requestDataToSign.setContentTypeMd5(contentMd5);
94  
95        template.header("Content-MD5", contentMd5);
96      }
97  
98      LOG.debug("Client data to sign: {}", requestDataToSign.stringToSign());
99  
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 }