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 ch.boye.httpclientandroidlib.impl.auth;
29 
30 import java.io.IOException;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Queue;
34 
35 import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
36 /* LogFactory removed by HttpClient for Android script. */
37 import ch.boye.httpclientandroidlib.Header;
38 import ch.boye.httpclientandroidlib.HttpException;
39 import ch.boye.httpclientandroidlib.HttpHost;
40 import ch.boye.httpclientandroidlib.HttpRequest;
41 import ch.boye.httpclientandroidlib.HttpResponse;
42 import ch.boye.httpclientandroidlib.auth.AuthOption;
43 import ch.boye.httpclientandroidlib.auth.AuthProtocolState;
44 import ch.boye.httpclientandroidlib.auth.AuthScheme;
45 import ch.boye.httpclientandroidlib.auth.AuthState;
46 import ch.boye.httpclientandroidlib.auth.AuthenticationException;
47 import ch.boye.httpclientandroidlib.auth.ContextAwareAuthScheme;
48 import ch.boye.httpclientandroidlib.auth.Credentials;
49 import ch.boye.httpclientandroidlib.auth.MalformedChallengeException;
50 import ch.boye.httpclientandroidlib.client.AuthenticationStrategy;
51 import ch.boye.httpclientandroidlib.protocol.HttpContext;
52 import ch.boye.httpclientandroidlib.util.Asserts;
53 
54 /**
55  * @since 4.3
56  */
57 public class HttpAuthenticator {
58 
59     public HttpClientAndroidLog log;
60 
HttpAuthenticator(final HttpClientAndroidLog log)61     public HttpAuthenticator(final HttpClientAndroidLog log) {
62         super();
63         this.log = log != null ? log : new HttpClientAndroidLog(getClass());
64     }
65 
HttpAuthenticator()66     public HttpAuthenticator() {
67         this(null);
68     }
69 
isAuthenticationRequested( final HttpHost host, final HttpResponse response, final AuthenticationStrategy authStrategy, final AuthState authState, final HttpContext context)70     public boolean isAuthenticationRequested(
71             final HttpHost host,
72             final HttpResponse response,
73             final AuthenticationStrategy authStrategy,
74             final AuthState authState,
75             final HttpContext context) {
76         if (authStrategy.isAuthenticationRequested(host, response, context)) {
77             this.log.debug("Authentication required");
78             if (authState.getState() == AuthProtocolState.SUCCESS) {
79                 authStrategy.authFailed(host, authState.getAuthScheme(), context);
80             }
81             return true;
82         } else {
83             switch (authState.getState()) {
84             case CHALLENGED:
85             case HANDSHAKE:
86                 this.log.debug("Authentication succeeded");
87                 authState.setState(AuthProtocolState.SUCCESS);
88                 authStrategy.authSucceeded(host, authState.getAuthScheme(), context);
89                 break;
90             case SUCCESS:
91                 break;
92             default:
93                 authState.setState(AuthProtocolState.UNCHALLENGED);
94             }
95             return false;
96         }
97     }
98 
handleAuthChallenge( final HttpHost host, final HttpResponse response, final AuthenticationStrategy authStrategy, final AuthState authState, final HttpContext context)99     public boolean handleAuthChallenge(
100             final HttpHost host,
101             final HttpResponse response,
102             final AuthenticationStrategy authStrategy,
103             final AuthState authState,
104             final HttpContext context) {
105         try {
106             if (this.log.isDebugEnabled()) {
107                 this.log.debug(host.toHostString() + " requested authentication");
108             }
109             final Map<String, Header> challenges = authStrategy.getChallenges(host, response, context);
110             if (challenges.isEmpty()) {
111                 this.log.debug("Response contains no authentication challenges");
112                 return false;
113             }
114 
115             final AuthScheme authScheme = authState.getAuthScheme();
116             switch (authState.getState()) {
117             case FAILURE:
118                 return false;
119             case SUCCESS:
120                 authState.reset();
121                 break;
122             case CHALLENGED:
123             case HANDSHAKE:
124                 if (authScheme == null) {
125                     this.log.debug("Auth scheme is null");
126                     authStrategy.authFailed(host, null, context);
127                     authState.reset();
128                     authState.setState(AuthProtocolState.FAILURE);
129                     return false;
130                 }
131             case UNCHALLENGED:
132                 if (authScheme != null) {
133                     final String id = authScheme.getSchemeName();
134                     final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH));
135                     if (challenge != null) {
136                         this.log.debug("Authorization challenge processed");
137                         authScheme.processChallenge(challenge);
138                         if (authScheme.isComplete()) {
139                             this.log.debug("Authentication failed");
140                             authStrategy.authFailed(host, authState.getAuthScheme(), context);
141                             authState.reset();
142                             authState.setState(AuthProtocolState.FAILURE);
143                             return false;
144                         } else {
145                             authState.setState(AuthProtocolState.HANDSHAKE);
146                             return true;
147                         }
148                     } else {
149                         authState.reset();
150                         // Retry authentication with a different scheme
151                     }
152                 }
153             }
154             final Queue<AuthOption> authOptions = authStrategy.select(challenges, host, response, context);
155             if (authOptions != null && !authOptions.isEmpty()) {
156                 if (this.log.isDebugEnabled()) {
157                     this.log.debug("Selected authentication options: " + authOptions);
158                 }
159                 authState.setState(AuthProtocolState.CHALLENGED);
160                 authState.update(authOptions);
161                 return true;
162             } else {
163                 return false;
164             }
165         } catch (final MalformedChallengeException ex) {
166             if (this.log.isWarnEnabled()) {
167                 this.log.warn("Malformed challenge: " +  ex.getMessage());
168             }
169             authState.reset();
170             return false;
171         }
172     }
173 
generateAuthResponse( final HttpRequest request, final AuthState authState, final HttpContext context)174     public void generateAuthResponse(
175             final HttpRequest request,
176             final AuthState authState,
177             final HttpContext context) throws HttpException, IOException {
178         AuthScheme authScheme = authState.getAuthScheme();
179         Credentials creds = authState.getCredentials();
180         switch (authState.getState()) {
181         case FAILURE:
182             return;
183         case SUCCESS:
184             ensureAuthScheme(authScheme);
185             if (authScheme.isConnectionBased()) {
186                 return;
187             }
188             break;
189         case CHALLENGED:
190             final Queue<AuthOption> authOptions = authState.getAuthOptions();
191             if (authOptions != null) {
192                 while (!authOptions.isEmpty()) {
193                     final AuthOption authOption = authOptions.remove();
194                     authScheme = authOption.getAuthScheme();
195                     creds = authOption.getCredentials();
196                     authState.update(authScheme, creds);
197                     if (this.log.isDebugEnabled()) {
198                         this.log.debug("Generating response to an authentication challenge using "
199                                 + authScheme.getSchemeName() + " scheme");
200                     }
201                     try {
202                         final Header header = doAuth(authScheme, creds, request, context);
203                         request.addHeader(header);
204                         break;
205                     } catch (final AuthenticationException ex) {
206                         if (this.log.isWarnEnabled()) {
207                             this.log.warn(authScheme + " authentication error: " + ex.getMessage());
208                         }
209                     }
210                 }
211                 return;
212             } else {
213                 ensureAuthScheme(authScheme);
214             }
215         }
216         if (authScheme != null) {
217             try {
218                 final Header header = doAuth(authScheme, creds, request, context);
219                 request.addHeader(header);
220             } catch (final AuthenticationException ex) {
221                 if (this.log.isErrorEnabled()) {
222                     this.log.error(authScheme + " authentication error: " + ex.getMessage());
223                 }
224             }
225         }
226     }
227 
ensureAuthScheme(final AuthScheme authScheme)228     private void ensureAuthScheme(final AuthScheme authScheme) {
229         Asserts.notNull(authScheme, "Auth scheme");
230     }
231 
232     @SuppressWarnings("deprecation")
doAuth( final AuthScheme authScheme, final Credentials creds, final HttpRequest request, final HttpContext context)233     private Header doAuth(
234             final AuthScheme authScheme,
235             final Credentials creds,
236             final HttpRequest request,
237             final HttpContext context) throws AuthenticationException {
238         if (authScheme instanceof ContextAwareAuthScheme) {
239             return ((ContextAwareAuthScheme) authScheme).authenticate(creds, request, context);
240         } else {
241             return authScheme.authenticate(creds, request);
242         }
243     }
244 
245 }
246