1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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(new String[] {
72 AuthSchemes.SPNEGO,
73 AuthSchemes.KERBEROS,
74 AuthSchemes.NTLM,
75 AuthSchemes.DIGEST,
76 AuthSchemes.BASIC
77 }));
78
79 private final int challengeCode;
80 private final String headerName;
81
82 AuthenticationStrategyImpl(final int challengeCode, final String headerName) {
83 super();
84 this.challengeCode = challengeCode;
85 this.headerName = headerName;
86 }
87
88 public boolean isAuthenticationRequested(
89 final HttpHost authhost,
90 final HttpResponse response,
91 final HttpContext context) {
92 Args.notNull(response, "HTTP response");
93 final int status = response.getStatusLine().getStatusCode();
94 return status == this.challengeCode;
95 }
96
97 public Map<String, Header> getChallenges(
98 final HttpHost authhost,
99 final HttpResponse response,
100 final HttpContext context) throws MalformedChallengeException {
101 Args.notNull(response, "HTTP response");
102 final Header[] headers = response.getHeaders(this.headerName);
103 final Map<String, Header> map = new HashMap<String, Header>(headers.length);
104 for (final Header header : headers) {
105 CharArrayBuffer buffer;
106 int pos;
107 if (header instanceof FormattedHeader) {
108 buffer = ((FormattedHeader) header).getBuffer();
109 pos = ((FormattedHeader) header).getValuePos();
110 } else {
111 final String s = header.getValue();
112 if (s == null) {
113 throw new MalformedChallengeException("Header value is null");
114 }
115 buffer = new CharArrayBuffer(s.length());
116 buffer.append(s);
117 pos = 0;
118 }
119 while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) {
120 pos++;
121 }
122 final int beginIndex = pos;
123 while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) {
124 pos++;
125 }
126 final int endIndex = pos;
127 final String s = buffer.substring(beginIndex, endIndex);
128 map.put(s.toLowerCase(Locale.US), header);
129 }
130 return map;
131 }
132
133 abstract Collection<String> getPreferredAuthSchemes(RequestConfig config);
134
135 public Queue<AuthOption> select(
136 final Map<String, Header> challenges,
137 final HttpHost authhost,
138 final HttpResponse response,
139 final HttpContext context) throws MalformedChallengeException {
140 Args.notNull(challenges, "Map of auth challenges");
141 Args.notNull(authhost, "Host");
142 Args.notNull(response, "HTTP response");
143 Args.notNull(context, "HTTP context");
144 final HttpClientContext clientContext = HttpClientContext.adapt(context);
145
146 final Queue<AuthOption> options = new LinkedList<AuthOption>();
147 final Lookup<AuthSchemeProvider> registry = clientContext.getAuthSchemeRegistry();
148 if (registry == null) {
149 this.log.debug("Auth scheme registry not set in the context");
150 return options;
151 }
152 final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
153 if (credsProvider == null) {
154 this.log.debug("Credentials provider not set in the context");
155 return options;
156 }
157 final RequestConfig config = clientContext.getRequestConfig();
158 Collection<String> authPrefs = getPreferredAuthSchemes(config);
159 if (authPrefs == null) {
160 authPrefs = DEFAULT_SCHEME_PRIORITY;
161 }
162 if (this.log.isDebugEnabled()) {
163 this.log.debug("Authentication schemes in the order of preference: " + authPrefs);
164 }
165
166 for (final String id: authPrefs) {
167 final Header challenge = challenges.get(id.toLowerCase(Locale.US));
168 if (challenge != null) {
169 final AuthSchemeProvider authSchemeProvider = registry.lookup(id);
170 if (authSchemeProvider == null) {
171 if (this.log.isWarnEnabled()) {
172 this.log.warn("Authentication scheme " + id + " not supported");
173
174 }
175 continue;
176 }
177 final AuthScheme authScheme = authSchemeProvider.create(context);
178 authScheme.processChallenge(challenge);
179
180 final AuthScope authScope = new AuthScope(
181 authhost.getHostName(),
182 authhost.getPort(),
183 authScheme.getRealm(),
184 authScheme.getSchemeName());
185
186 final Credentials credentials = credsProvider.getCredentials(authScope);
187 if (credentials != null) {
188 options.add(new AuthOption(authScheme, credentials));
189 }
190 } else {
191 if (this.log.isDebugEnabled()) {
192 this.log.debug("Challenge for " + id + " authentication scheme not available");
193
194 }
195 }
196 }
197 return options;
198 }
199
200 public void authSucceeded(
201 final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
202 Args.notNull(authhost, "Host");
203 Args.notNull(authScheme, "Auth scheme");
204 Args.notNull(context, "HTTP context");
205
206 final HttpClientContext clientContext = HttpClientContext.adapt(context);
207
208 if (isCachable(authScheme)) {
209 AuthCache authCache = clientContext.getAuthCache();
210 if (authCache == null) {
211 authCache = new BasicAuthCache();
212 clientContext.setAuthCache(authCache);
213 }
214 if (this.log.isDebugEnabled()) {
215 this.log.debug("Caching '" + authScheme.getSchemeName() +
216 "' auth scheme for " + authhost);
217 }
218 authCache.put(authhost, authScheme);
219 }
220 }
221
222 protected boolean isCachable(final AuthScheme authScheme) {
223 if (authScheme == null || !authScheme.isComplete()) {
224 return false;
225 }
226 final String schemeName = authScheme.getSchemeName();
227 return schemeName.equalsIgnoreCase(AuthSchemes.BASIC) ||
228 schemeName.equalsIgnoreCase(AuthSchemes.DIGEST);
229 }
230
231 public void authFailed(
232 final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
233 Args.notNull(authhost, "Host");
234 Args.notNull(context, "HTTP context");
235
236 final HttpClientContext clientContext = HttpClientContext.adapt(context);
237
238 final AuthCache authCache = clientContext.getAuthCache();
239 if (authCache != null) {
240 if (this.log.isDebugEnabled()) {
241 this.log.debug("Clearing cached auth scheme for " + authhost);
242 }
243 authCache.remove(authhost);
244 }
245 }
246
247 }