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 abstract class AuthenticationProtocolHandler implements ProtocolHandler
39  {
40      public static final int DEFAULT_MAX_CONTENT_LENGTH = 4096;
41      public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
42      private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE);
43      private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication";
44  
45      private final HttpClient client;
46      private final int maxContentLength;
47      private final ResponseNotifier notifier;
48  
49      protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
50      {
51          this.client = client;
52          this.maxContentLength = maxContentLength;
53          this.notifier = new ResponseNotifier(client);
54      }
55  
56      protected HttpClient getHttpClient()
57      {
58          return client;
59      }
60  
61      protected abstract HttpHeader getAuthenticateHeader();
62  
63      protected abstract HttpHeader getAuthorizationHeader();
64  
65      protected abstract URI getAuthenticationURI(Request request);
66  
67      @Override
68      public Response.Listener getResponseListener()
69      {
70          // Return new instances every time to keep track of the response content
71          return new AuthenticationListener();
72      }
73  
74      private class AuthenticationListener extends BufferingResponseListener
75      {
76          private AuthenticationListener()
77          {
78              super(maxContentLength);
79          }
80  
81          @Override
82          public void onComplete(Result result)
83          {
84              Request request = result.getRequest();
85              ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
86              if (result.isFailed())
87              {
88                  Throwable failure = result.getFailure();
89                  LOG.debug("Authentication challenge failed {}", failure);
90                  forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
91                  return;
92              }
93  
94              HttpConversation conversation = client.getConversation(request.getConversationID(), false);
95              if (conversation.getAttribute(AUTHENTICATION_ATTRIBUTE) != null)
96              {
97                  // We have already tried to authenticate, but we failed again
98                  LOG.debug("Bad credentials for {}", request);
99                  forwardSuccessComplete(request, response);
100                 return;
101             }
102 
103             HttpHeader header = getAuthenticateHeader();
104             List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
105             if (headerInfos.isEmpty())
106             {
107                 LOG.debug("Authentication challenge without {} header", header);
108                 forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
109                 return;
110             }
111 
112             Authentication authentication = null;
113             Authentication.HeaderInfo headerInfo = null;
114             URI uri = getAuthenticationURI(request);
115             if (uri != null)
116             {
117                 for (Authentication.HeaderInfo element : headerInfos)
118                 {
119                     authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
120                     if (authentication != null)
121                     {
122                         headerInfo = element;
123                         break;
124                     }
125                 }
126             }
127             if (authentication == null)
128             {
129                 LOG.debug("No authentication available for {}", request);
130                 forwardSuccessComplete(request, response);
131                 return;
132             }
133 
134             final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
135             LOG.debug("Authentication result {}", authnResult);
136             if (authnResult == null)
137             {
138                 forwardSuccessComplete(request, response);
139                 return;
140             }
141 
142             conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true);
143 
144             Request newRequest = client.copyRequest(request, request.getURI());
145             authnResult.apply(newRequest);
146             newRequest.onResponseSuccess(new Response.SuccessListener()
147             {
148                 @Override
149                 public void onSuccess(Response response)
150                 {
151                     client.getAuthenticationStore().addAuthenticationResult(authnResult);
152                 }
153             }).send(null);
154         }
155 
156         private void forwardSuccessComplete(Request request, Response response)
157         {
158             HttpConversation conversation = client.getConversation(request.getConversationID(), false);
159             conversation.updateResponseListeners(null);
160             notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
161         }
162 
163         private void forwardFailureComplete(Request request, Throwable requestFailure, Response response, Throwable responseFailure)
164         {
165             HttpConversation conversation = client.getConversation(request.getConversationID(), false);
166             conversation.updateResponseListeners(null);
167             notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
168         }
169 
170         private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
171         {
172             // TODO: these should be ordered by strength
173             List<Authentication.HeaderInfo> result = new ArrayList<>();
174             List<String> values = Collections.list(response.getHeaders().getValues(header.asString()));
175             for (String value : values)
176             {
177                 Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
178                 if (matcher.matches())
179                 {
180                     String type = matcher.group(1);
181                     String realm = matcher.group(2);
182                     String params = matcher.group(3);
183                     Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
184                     result.add(headerInfo);
185                 }
186             }
187             return result;
188         }
189     }
190 }