001/* 002 * Copyright 2012-2016 UnboundID Corp. 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim.sdk; 019 020import java.io.IOException; 021 022import org.apache.http.HttpException; 023import org.apache.http.HttpHost; 024import org.apache.http.HttpRequest; 025import org.apache.http.HttpRequestInterceptor; 026import org.apache.http.auth.AuthProtocolState; 027import org.apache.http.auth.AuthScheme; 028import org.apache.http.auth.AuthScope; 029import org.apache.http.auth.AuthState; 030import org.apache.http.auth.Credentials; 031import org.apache.http.client.AuthCache; 032import org.apache.http.client.CredentialsProvider; 033import org.apache.http.client.protocol.ClientContext; 034import org.apache.http.conn.scheme.Scheme; 035import org.apache.http.conn.scheme.SchemeRegistry; 036import org.apache.http.impl.auth.BasicScheme; 037import org.apache.http.impl.client.BasicAuthCache; 038import org.apache.http.protocol.ExecutionContext; 039import org.apache.http.protocol.HttpContext; 040 041 042/** 043 * This class can be used to configure the Apache Http Client for preemptive 044 * authentication. In this mode, the client will send the basic authentication 045 * response even before the server gives an unauthorized response in certain 046 * situations. This reduces the overhead of making requests over authenticated 047 * connections. 048 * 049 * This behavior conforms to RFC2617: A client MAY preemptively send the 050 * corresponding Authorization header with requests for resources in that space 051 * without receipt of another challenge from the server. Similarly, when a 052 * client sends a request to a proxy, it may reuse a userid and password in the 053 * Proxy-Authorization header field without receiving another challenge from the 054 * proxy server. 055 * 056 * The Apache Http Client does not support preemptive authentication out of the 057 * box, because if misused or used incorrectly the preemptive authentication can 058 * lead to significant security issues, such as sending user credentials in 059 * clear text to an unauthorized third party. 060 */ 061public class PreemptiveAuthInterceptor implements HttpRequestInterceptor 062{ 063 064 /** 065 * Constructs a new PreemptiveAuthInterceptor. It is important that this is 066 * added as the <b>first</b> request interceptor in the chain. You can do this 067 * by making sure the second parameter is zero when adding the interceptor: 068 * <p> 069 * <code> 070 * httpClient.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0); 071 * </code> 072 */ 073 public PreemptiveAuthInterceptor() 074 { 075 //No implementation necessary. 076 } 077 078 /** 079 * Constructs a new PreemptiveAuthInterceptor. It is important that this is 080 * added as the <b>first</b> request interceptor in the chain. You can do this 081 * by making sure the second parameter is zero when adding the interceptor: 082 * <p> 083 * <code> 084 * httpClient.addRequestInterceptor( 085 * new PreemptiveAuthInterceptor(new BasicScheme(), credentials), 0); 086 * </code> 087 * 088 * <b>NOTE: This constructor is deprecated and may be removed in a future 089 * release.</b> 090 * 091 * @param authScheme The AuthScheme to use. This may not be null. 092 * @param credentials The Credentials to use. This may not be null. 093 */ 094 @Deprecated 095 public PreemptiveAuthInterceptor(final AuthScheme authScheme, 096 final Credentials credentials) 097 { 098 //No implementation necessary. 099 } 100 101 /** 102 * {@inheritDoc} 103 */ 104 @Override 105 public void process(final HttpRequest request, final HttpContext context) 106 throws HttpException, IOException 107 { 108 HttpHost target = (HttpHost) context.getAttribute( 109 ExecutionContext.HTTP_TARGET_HOST); 110 if(target.getPort() < 0) 111 { 112 SchemeRegistry schemeRegistry = (SchemeRegistry) context.getAttribute( 113 ClientContext.SCHEME_REGISTRY); 114 Scheme scheme = schemeRegistry.getScheme(target); 115 target = new HttpHost(target.getHostName(), 116 scheme.resolvePort(target.getPort()), target.getSchemeName()); 117 } 118 119 AuthCache authCache = (AuthCache) context.getAttribute( 120 ClientContext.AUTH_CACHE); 121 if(authCache == null) 122 { 123 authCache = new BasicAuthCache(); 124 BasicScheme basicAuth = new BasicScheme(); 125 authCache.put(target, basicAuth); 126 context.setAttribute(ClientContext.AUTH_CACHE, authCache); 127 return; 128 } 129 130 CredentialsProvider credsProvider = 131 (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER); 132 if(credsProvider == null) 133 { 134 return; 135 } 136 137 final AuthState targetState = (AuthState) context.getAttribute( 138 ClientContext.TARGET_AUTH_STATE); 139 if(targetState != null && 140 targetState.getState() == AuthProtocolState.UNCHALLENGED) 141 { 142 final AuthScheme authScheme = authCache.get(target); 143 if(authScheme != null) 144 { 145 doPreemptiveAuth(target, authScheme, targetState, credsProvider); 146 } 147 } 148 149 final HttpHost proxy = (HttpHost) context.getAttribute( 150 ExecutionContext.HTTP_PROXY_HOST); 151 final AuthState proxyState = (AuthState) context.getAttribute( 152 ClientContext.PROXY_AUTH_STATE); 153 if(proxy != null && proxyState != null && 154 proxyState.getState() == AuthProtocolState.UNCHALLENGED) 155 { 156 final AuthScheme authScheme = authCache.get(proxy); 157 if(authScheme != null) 158 { 159 doPreemptiveAuth(proxy, authScheme, proxyState, credsProvider); 160 } 161 } 162 } 163 164 /** 165 * Method to update the AuthState in order to preemptively supply the 166 * credentials to the server. 167 * 168 * @param host the HttpHost which we're authenticating to 169 * @param authScheme the AuthScheme in use 170 * @param authState the AuthState object from the HttpContext 171 * @param credsProvider the CredentialsProvider which has the username and 172 * password 173 */ 174 private void doPreemptiveAuth( 175 final HttpHost host, 176 final AuthScheme authScheme, 177 final AuthState authState, 178 final CredentialsProvider credsProvider) 179 { 180 final String schemeName = authScheme.getSchemeName(); 181 182 final AuthScope authScope = new AuthScope( 183 host, AuthScope.ANY_REALM, schemeName); 184 final Credentials creds = credsProvider.getCredentials(authScope); 185 186 if(creds != null) 187 { 188 if("BASIC".equalsIgnoreCase(schemeName)) 189 { 190 authState.setState(AuthProtocolState.CHALLENGED); 191 } 192 else 193 { 194 authState.setState(AuthProtocolState.SUCCESS); 195 } 196 authState.update(authScheme, creds); 197 } 198 } 199}