View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client;
20  
21  import java.net.URI;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  
28  import org.eclipse.jetty.client.api.Authentication;
29  import org.eclipse.jetty.client.api.ContentResponse;
30  import org.eclipse.jetty.client.api.Request;
31  import org.eclipse.jetty.client.api.Response;
32  import org.eclipse.jetty.client.api.Result;
33  import org.eclipse.jetty.client.util.BufferingResponseListener;
34  import org.eclipse.jetty.http.HttpHeader;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  
38  public class AuthenticationProtocolHandler implements ProtocolHandler
39  {
40      public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
41      private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\".*", Pattern.CASE_INSENSITIVE);
42  
43      private final HttpClient client;
44      private final int maxContentLength;
45      private final ResponseNotifier notifier;
46  
47      public AuthenticationProtocolHandler(HttpClient client)
48      {
49          this(client, 4096);
50      }
51  
52      public AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
53      {
54          this.client = client;
55          this.maxContentLength = maxContentLength;
56          this.notifier = new ResponseNotifier(client);
57      }
58  
59      @Override
60      public boolean accept(Request request, Response response)
61      {
62          return response.getStatus() == 401;
63      }
64  
65      @Override
66      public Response.Listener getResponseListener()
67      {
68          // Return new instances every time to keep track of the response content
69          return new AuthenticationListener();
70      }
71  
72      private class AuthenticationListener extends BufferingResponseListener
73      {
74          private AuthenticationListener()
75          {
76              super(maxContentLength);
77          }
78  
79          @Override
80          public void onComplete(Result result)
81          {
82              Request request = result.getRequest();
83              ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
84              if (result.isFailed())
85              {
86                  Throwable failure = result.getFailure();
87                  LOG.debug("Authentication challenge failed {}", failure);
88                  forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
89                  return;
90              }
91  
92              List<WWWAuthenticate> wwwAuthenticates = parseWWWAuthenticate(response);
93              if (wwwAuthenticates.isEmpty())
94              {
95                  LOG.debug("Authentication challenge without WWW-Authenticate header");
96                  forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
97                  return;
98              }
99  
100             final URI uri = request.getURI();
101             Authentication authentication = null;
102             WWWAuthenticate wwwAuthenticate = null;
103             for (WWWAuthenticate wwwAuthn : wwwAuthenticates)
104             {
105                 authentication = client.getAuthenticationStore().findAuthentication(wwwAuthn.type, uri, wwwAuthn.realm);
106                 if (authentication != null)
107                 {
108                     wwwAuthenticate = wwwAuthn;
109                     break;
110                 }
111             }
112             if (authentication == null)
113             {
114                 LOG.debug("No authentication available for {}", request);
115                 forwardSuccessComplete(request, response);
116                 return;
117             }
118 
119             HttpConversation conversation = client.getConversation(request.getConversationID(), false);
120             final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
121             LOG.debug("Authentication result {}", authnResult);
122             if (authnResult == null)
123             {
124                 forwardSuccessComplete(request, response);
125                 return;
126             }
127 
128             Request newRequest = client.copyRequest(request, uri);
129             authnResult.apply(newRequest);
130             newRequest.onResponseSuccess(new Response.SuccessListener()
131             {
132                 @Override
133                 public void onSuccess(Response response)
134                 {
135                     client.getAuthenticationStore().addAuthenticationResult(authnResult);
136                 }
137             }).send(null);
138         }
139 
140         private void forwardSuccessComplete(Request request, Response response)
141         {
142             HttpConversation conversation = client.getConversation(request.getConversationID(), false);
143             conversation.updateResponseListeners(null);
144             notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
145         }
146 
147         private void forwardFailureComplete(Request request, Throwable requestFailure, Response response, Throwable responseFailure)
148         {
149             HttpConversation conversation = client.getConversation(request.getConversationID(), false);
150             conversation.updateResponseListeners(null);
151             notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
152         }
153 
154         private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
155         {
156             // TODO: these should be ordered by strength
157             List<WWWAuthenticate> result = new ArrayList<>();
158             List<String> values = Collections.list(response.getHeaders().getValues(HttpHeader.WWW_AUTHENTICATE.asString()));
159             for (String value : values)
160             {
161                 Matcher matcher = WWW_AUTHENTICATE_PATTERN.matcher(value);
162                 if (matcher.matches())
163                 {
164                     String type = matcher.group(1);
165                     String realm = matcher.group(2);
166                     WWWAuthenticate wwwAuthenticate = new WWWAuthenticate(value, type, realm);
167                     result.add(wwwAuthenticate);
168                 }
169             }
170             return result;
171         }
172     }
173 
174     private class WWWAuthenticate
175     {
176         private final String value;
177         private final String type;
178         private final String realm;
179 
180         public WWWAuthenticate(String value, String type, String realm)
181         {
182             this.value = value;
183             this.type = type;
184             this.realm = realm;
185         }
186     }
187 }