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}