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