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             URI uri = getAuthenticationURI(request);
113             Authentication authentication = null;
114             Authentication.HeaderInfo headerInfo = null;
115             for (Authentication.HeaderInfo element : headerInfos)
116             {
117                 authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
118                 if (authentication != null)
119                 {
120                     headerInfo = element;
121                     break;
122                 }
123             }
124             if (authentication == null)
125             {
126                 LOG.debug("No authentication available for {}", request);
127                 forwardSuccessComplete(request, response);
128                 return;
129             }
130 
131             final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
132             LOG.debug("Authentication result {}", authnResult);
133             if (authnResult == null)
134             {
135                 forwardSuccessComplete(request, response);
136                 return;
137             }
138 
139             conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true);
140 
141             Request newRequest = client.copyRequest(request, request.getURI());
142             authnResult.apply(newRequest);
143             newRequest.onResponseSuccess(new Response.SuccessListener()
144             {
145                 @Override
146                 public void onSuccess(Response response)
147                 {
148                     client.getAuthenticationStore().addAuthenticationResult(authnResult);
149                 }
150             }).send(null);
151         }
152 
153         private void forwardSuccessComplete(Request request, Response response)
154         {
155             HttpConversation conversation = client.getConversation(request.getConversationID(), false);
156             conversation.updateResponseListeners(null);
157             notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
158         }
159 
160         private void forwardFailureComplete(Request request, Throwable requestFailure, Response response, Throwable responseFailure)
161         {
162             HttpConversation conversation = client.getConversation(request.getConversationID(), false);
163             conversation.updateResponseListeners(null);
164             notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
165         }
166 
167         private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
168         {
169             // TODO: these should be ordered by strength
170             List<Authentication.HeaderInfo> result = new ArrayList<>();
171             List<String> values = Collections.list(response.getHeaders().getValues(header.asString()));
172             for (String value : values)
173             {
174                 Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
175                 if (matcher.matches())
176                 {
177                     String type = matcher.group(1);
178                     String realm = matcher.group(2);
179                     String params = matcher.group(3);
180                     Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
181                     result.add(headerInfo);
182                 }
183             }
184             return result;
185         }
186     }
187 }