1 /* 2 * Copyright 2015, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package com.google.auth.http; 33 34 import com.google.api.client.http.HttpHeaders; 35 import com.google.api.client.http.HttpRequest; 36 import com.google.api.client.http.HttpRequestInitializer; 37 import com.google.api.client.http.HttpResponse; 38 import com.google.api.client.http.HttpStatusCodes; 39 import com.google.api.client.http.HttpUnsuccessfulResponseHandler; 40 import com.google.api.client.util.Preconditions; 41 import com.google.auth.Credentials; 42 import java.io.IOException; 43 import java.net.URI; 44 import java.util.ArrayList; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.logging.Level; 48 import java.util.logging.Logger; 49 import java.util.regex.Pattern; 50 51 /** A wrapper for using Credentials with the Google API Client Libraries for Java with Http. */ 52 public class HttpCredentialsAdapter 53 implements HttpRequestInitializer, HttpUnsuccessfulResponseHandler { 54 55 private static final Logger LOGGER = Logger.getLogger(HttpCredentialsAdapter.class.getName()); 56 57 /** 58 * In case an abnormal HTTP response is received with {@code WWW-Authenticate} header, and its 59 * value contains this error pattern, we will try to refresh the token. 60 */ 61 private static final Pattern INVALID_TOKEN_ERROR = 62 Pattern.compile("\\s*error\\s*=\\s*\"?invalid_token\"?"); 63 64 private final Credentials credentials; 65 66 /** @param credentials Credentials instance to adapt for HTTP */ HttpCredentialsAdapter(Credentials credentials)67 public HttpCredentialsAdapter(Credentials credentials) { 68 Preconditions.checkNotNull(credentials); 69 this.credentials = credentials; 70 } 71 72 /** A getter for the credentials instance being used */ getCredentials()73 public Credentials getCredentials() { 74 return credentials; 75 } 76 77 /** 78 * {@inheritDoc} 79 * 80 * <p>Initialize the HTTP request prior to execution. 81 * 82 * @param request HTTP request 83 */ 84 @Override initialize(HttpRequest request)85 public void initialize(HttpRequest request) throws IOException { 86 request.setUnsuccessfulResponseHandler(this); 87 88 if (!credentials.hasRequestMetadata()) { 89 return; 90 } 91 HttpHeaders requestHeaders = request.getHeaders(); 92 URI uri = null; 93 if (request.getUrl() != null) { 94 uri = request.getUrl().toURI(); 95 } 96 Map<String, List<String>> credentialHeaders = credentials.getRequestMetadata(uri); 97 if (credentialHeaders == null) { 98 return; 99 } 100 for (Map.Entry<String, List<String>> entry : credentialHeaders.entrySet()) { 101 String headerName = entry.getKey(); 102 List<String> requestValues = new ArrayList<>(); 103 requestValues.addAll(entry.getValue()); 104 requestHeaders.put(headerName, requestValues); 105 } 106 } 107 108 /** 109 * {@inheritDoc} 110 * 111 * <p>Checks if {@code WWW-Authenticate} exists and contains a "Bearer" value (see <a 112 * href="http://tools.ietf.org/html/rfc6750#section-3.1">rfc6750 section 3.1</a> for more 113 * details). If so, it refreshes the token in case the error code contains {@code invalid_token}. 114 * If there is no "Bearer" in {@code WWW-Authenticate} and the status code is {@link 115 * HttpStatusCodes#STATUS_CODE_UNAUTHORIZED} it refreshes the token. If the token refresh throws 116 * an I/O exception, this implementation will log the exception and return {@code false}. 117 */ 118 @Override handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry)119 public boolean handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry) { 120 boolean refreshToken = false; 121 boolean bearer = false; 122 123 List<String> authenticateList = response.getHeaders().getAuthenticateAsList(); 124 125 // if authenticate list is not null we will check if one of the entries contains "Bearer" 126 if (authenticateList != null) { 127 for (String authenticate : authenticateList) { 128 if (authenticate.startsWith(InternalAuthHttpConstants.BEARER_PREFIX)) { 129 // mark that we found a "Bearer" value, and check if there is a invalid_token error 130 bearer = true; 131 refreshToken = INVALID_TOKEN_ERROR.matcher(authenticate).find(); 132 break; 133 } 134 } 135 } 136 137 // if "Bearer" wasn't found, we will refresh the token, if we got 401 138 if (!bearer) { 139 refreshToken = response.getStatusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED; 140 } 141 142 if (refreshToken) { 143 try { 144 credentials.refresh(); 145 initialize(request); 146 return true; 147 } catch (IOException exception) { 148 LOGGER.log(Level.SEVERE, "unable to refresh token", exception); 149 } 150 } 151 return false; 152 } 153 } 154