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}