View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.client;
29  
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Queue;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.apache.http.FormattedHeader;
43  import org.apache.http.Header;
44  import org.apache.http.HttpHost;
45  import org.apache.http.HttpResponse;
46  import org.apache.http.annotation.Immutable;
47  import org.apache.http.auth.AuthOption;
48  import org.apache.http.auth.AuthScheme;
49  import org.apache.http.auth.AuthSchemeProvider;
50  import org.apache.http.auth.AuthScope;
51  import org.apache.http.auth.Credentials;
52  import org.apache.http.auth.MalformedChallengeException;
53  import org.apache.http.client.AuthCache;
54  import org.apache.http.client.AuthenticationStrategy;
55  import org.apache.http.client.CredentialsProvider;
56  import org.apache.http.client.config.AuthSchemes;
57  import org.apache.http.client.config.RequestConfig;
58  import org.apache.http.client.protocol.HttpClientContext;
59  import org.apache.http.config.Lookup;
60  import org.apache.http.protocol.HTTP;
61  import org.apache.http.protocol.HttpContext;
62  import org.apache.http.util.Args;
63  import org.apache.http.util.CharArrayBuffer;
64  
65  @Immutable
66  abstract class AuthenticationStrategyImpl implements AuthenticationStrategy {
67  
68      private final Log log = LogFactory.getLog(getClass());
69  
70      private static final List<String> DEFAULT_SCHEME_PRIORITY =
71          Collections.unmodifiableList(Arrays.asList(
72                  AuthSchemes.SPNEGO,
73                  AuthSchemes.KERBEROS,
74                  AuthSchemes.NTLM,
75                  AuthSchemes.DIGEST,
76                  AuthSchemes.BASIC));
77  
78      private final int challengeCode;
79      private final String headerName;
80  
81      /**
82       * @param challengeCode for example SC_PROXY_AUTHENTICATION_REQUIRED or SC_UNAUTHORIZED
83       * @param headerName for example "Proxy-Authenticate" or "WWW-Authenticate"
84       */
85      AuthenticationStrategyImpl(final int challengeCode, final String headerName) {
86          super();
87          this.challengeCode = challengeCode;
88          this.headerName = headerName;
89      }
90  
91      @Override
92      public boolean isAuthenticationRequested(
93              final HttpHost authhost,
94              final HttpResponse response,
95              final HttpContext context) {
96          Args.notNull(response, "HTTP response");
97          final int status = response.getStatusLine().getStatusCode();
98          return status == this.challengeCode;
99      }
100 
101     /**
102      * Generates a map of challenge auth-scheme => Header entries
103      * @return map: key=lower-cased auth-scheme name, value=Header that contains the challenge
104      */
105     @Override
106     public Map<String, Header> getChallenges(
107             final HttpHost authhost,
108             final HttpResponse response,
109             final HttpContext context) throws MalformedChallengeException {
110         Args.notNull(response, "HTTP response");
111         final Header[] headers = response.getHeaders(this.headerName);
112         final Map<String, Header> map = new HashMap<String, Header>(headers.length);
113         for (final Header header : headers) {
114             final CharArrayBuffer buffer;
115             int pos;
116             if (header instanceof FormattedHeader) {
117                 buffer = ((FormattedHeader) header).getBuffer();
118                 pos = ((FormattedHeader) header).getValuePos();
119             } else {
120                 final String s = header.getValue();
121                 if (s == null) {
122                     throw new MalformedChallengeException("Header value is null");
123                 }
124                 buffer = new CharArrayBuffer(s.length());
125                 buffer.append(s);
126                 pos = 0;
127             }
128             while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) {
129                 pos++;
130             }
131             final int beginIndex = pos;
132             while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) {
133                 pos++;
134             }
135             final int endIndex = pos;
136             final String s = buffer.substring(beginIndex, endIndex);
137             map.put(s.toLowerCase(Locale.ROOT), header);
138         }
139         return map;
140     }
141 
142     abstract Collection<String> getPreferredAuthSchemes(RequestConfig config);
143 
144     @Override
145     public Queue<AuthOption> select(
146             final Map<String, Header> challenges,
147             final HttpHost authhost,
148             final HttpResponse response,
149             final HttpContext context) throws MalformedChallengeException {
150         Args.notNull(challenges, "Map of auth challenges");
151         Args.notNull(authhost, "Host");
152         Args.notNull(response, "HTTP response");
153         Args.notNull(context, "HTTP context");
154         final HttpClientContext clientContext = HttpClientContext.adapt(context);
155 
156         final Queue<AuthOption> options = new LinkedList<AuthOption>();
157         final Lookup<AuthSchemeProvider> registry = clientContext.getAuthSchemeRegistry();
158         if (registry == null) {
159             this.log.debug("Auth scheme registry not set in the context");
160             return options;
161         }
162         final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
163         if (credsProvider == null) {
164             this.log.debug("Credentials provider not set in the context");
165             return options;
166         }
167         final RequestConfig config = clientContext.getRequestConfig();
168         Collection<String> authPrefs = getPreferredAuthSchemes(config);
169         if (authPrefs == null) {
170             authPrefs = DEFAULT_SCHEME_PRIORITY;
171         }
172         if (this.log.isDebugEnabled()) {
173             this.log.debug("Authentication schemes in the order of preference: " + authPrefs);
174         }
175 
176         for (final String id: authPrefs) {
177             final Header challenge = challenges.get(id.toLowerCase(Locale.ROOT));
178             if (challenge != null) {
179                 final AuthSchemeProvider authSchemeProvider = registry.lookup(id);
180                 if (authSchemeProvider == null) {
181                     if (this.log.isWarnEnabled()) {
182                         this.log.warn("Authentication scheme " + id + " not supported");
183                         // Try again
184                     }
185                     continue;
186                 }
187                 final AuthScheme authScheme = authSchemeProvider.create(context);
188                 authScheme.processChallenge(challenge);
189 
190                 final AuthScope authScope = new AuthScope(
191                         authhost.getHostName(),
192                         authhost.getPort(),
193                         authScheme.getRealm(),
194                         authScheme.getSchemeName());
195 
196                 final Credentials credentials = credsProvider.getCredentials(authScope);
197                 if (credentials != null) {
198                     options.add(new AuthOption(authScheme, credentials));
199                 }
200             } else {
201                 if (this.log.isDebugEnabled()) {
202                     this.log.debug("Challenge for " + id + " authentication scheme not available");
203                     // Try again
204                 }
205             }
206         }
207         return options;
208     }
209 
210     @Override
211     public void authSucceeded(
212             final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
213         Args.notNull(authhost, "Host");
214         Args.notNull(authScheme, "Auth scheme");
215         Args.notNull(context, "HTTP context");
216 
217         final HttpClientContext clientContext = HttpClientContext.adapt(context);
218 
219         if (isCachable(authScheme)) {
220             AuthCache authCache = clientContext.getAuthCache();
221             if (authCache == null) {
222                 authCache = new BasicAuthCache();
223                 clientContext.setAuthCache(authCache);
224             }
225             if (this.log.isDebugEnabled()) {
226                 this.log.debug("Caching '" + authScheme.getSchemeName() +
227                         "' auth scheme for " + authhost);
228             }
229             authCache.put(authhost, authScheme);
230         }
231     }
232 
233     protected boolean isCachable(final AuthScheme authScheme) {
234         if (authScheme == null || !authScheme.isComplete()) {
235             return false;
236         }
237         final String schemeName = authScheme.getSchemeName();
238         return schemeName.equalsIgnoreCase(AuthSchemes.BASIC) ||
239                 schemeName.equalsIgnoreCase(AuthSchemes.DIGEST);
240     }
241 
242     @Override
243     public void authFailed(
244             final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
245         Args.notNull(authhost, "Host");
246         Args.notNull(context, "HTTP context");
247 
248         final HttpClientContext clientContext = HttpClientContext.adapt(context);
249 
250         final AuthCache authCache = clientContext.getAuthCache();
251         if (authCache != null) {
252             if (this.log.isDebugEnabled()) {
253                 this.log.debug("Clearing cached auth scheme for " + authhost);
254             }
255             authCache.remove(authhost);
256         }
257     }
258 
259 }