View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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 = 16*1024;
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();
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              HttpRequest request = (HttpRequest)result.getRequest();
85              ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
86              if (result.isFailed())
87              {
88                  Throwable failure = result.getFailure();
89                  if (LOG.isDebugEnabled())
90                      LOG.debug("Authentication challenge failed {}", failure);
91                  forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
92                  return;
93              }
94  
95              HttpConversation conversation = request.getConversation();
96              if (conversation.getAttribute(AUTHENTICATION_ATTRIBUTE) != null)
97              {
98                  // We have already tried to authenticate, but we failed again
99                  if (LOG.isDebugEnabled())
100                     LOG.debug("Bad credentials for {}", request);
101                 forwardSuccessComplete(request, response);
102                 return;
103             }
104 
105             HttpHeader header = getAuthenticateHeader();
106             List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
107             if (headerInfos.isEmpty())
108             {
109                 if (LOG.isDebugEnabled())
110                     LOG.debug("Authentication challenge without {} header", header);
111                 forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
112                 return;
113             }
114 
115             Authentication authentication = null;
116             Authentication.HeaderInfo headerInfo = null;
117             URI uri = getAuthenticationURI(request);
118             if (uri != null)
119             {
120                 for (Authentication.HeaderInfo element : headerInfos)
121                 {
122                     authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
123                     if (authentication != null)
124                     {
125                         headerInfo = element;
126                         break;
127                     }
128                 }
129             }
130             if (authentication == null)
131             {
132                 if (LOG.isDebugEnabled())
133                     LOG.debug("No authentication available for {}", request);
134                 forwardSuccessComplete(request, response);
135                 return;
136             }
137 
138             try
139             {
140                 final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
141                 if (LOG.isDebugEnabled())
142                     LOG.debug("Authentication result {}", authnResult);
143                 if (authnResult == null)
144                 {
145                     forwardSuccessComplete(request, response);
146                     return;
147                 }
148 
149                 conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true);
150 
151                 Request newRequest = client.copyRequest(request, request.getURI());
152                 authnResult.apply(newRequest);
153                 newRequest.onResponseSuccess(new Response.SuccessListener()
154                 {
155                     @Override
156                     public void onSuccess(Response response)
157                     {
158                         client.getAuthenticationStore().addAuthenticationResult(authnResult);
159                     }
160                 }).send(null);
161             }
162             catch (Throwable x)
163             {
164                 if (LOG.isDebugEnabled())
165                     LOG.debug("Authentication failed", x);
166                 forwardFailureComplete(request, null, response, x);
167             }
168         }
169 
170         private void forwardSuccessComplete(HttpRequest request, Response response)
171         {
172             HttpConversation conversation = request.getConversation();
173             conversation.updateResponseListeners(null);
174             notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
175         }
176 
177         private void forwardFailureComplete(HttpRequest request, Throwable requestFailure, Response response, Throwable responseFailure)
178         {
179             HttpConversation conversation = request.getConversation();
180             conversation.updateResponseListeners(null);
181             notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
182         }
183 
184         private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
185         {
186             // TODO: these should be ordered by strength
187             List<Authentication.HeaderInfo> result = new ArrayList<>();
188             List<String> values = Collections.list(response.getHeaders().getValues(header.asString()));
189             for (String value : values)
190             {
191                 Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
192                 if (matcher.matches())
193                 {
194                     String type = matcher.group(1);
195                     String realm = matcher.group(2);
196                     String params = matcher.group(3);
197                     Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
198                     result.add(headerInfo);
199                 }
200             }
201             return result;
202         }
203     }
204 }