1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.Connection;
30 import org.eclipse.jetty.client.api.ContentResponse;
31 import org.eclipse.jetty.client.api.Request;
32 import org.eclipse.jetty.client.api.Response;
33 import org.eclipse.jetty.client.api.Result;
34 import org.eclipse.jetty.client.util.BufferingResponseListener;
35 import org.eclipse.jetty.http.HttpHeader;
36 import org.eclipse.jetty.http.HttpMethod;
37 import org.eclipse.jetty.util.log.Log;
38 import org.eclipse.jetty.util.log.Logger;
39
40 public abstract class AuthenticationProtocolHandler implements ProtocolHandler
41 {
42 public static final int DEFAULT_MAX_CONTENT_LENGTH = 4096;
43 public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
44 private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE);
45 private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication";
46
47 private final HttpClient client;
48 private final int maxContentLength;
49 private final ResponseNotifier notifier;
50
51 protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
52 {
53 this.client = client;
54 this.maxContentLength = maxContentLength;
55 this.notifier = new ResponseNotifier();
56 }
57
58 protected HttpClient getHttpClient()
59 {
60 return client;
61 }
62
63 protected abstract HttpHeader getAuthenticateHeader();
64
65 protected abstract HttpHeader getAuthorizationHeader();
66
67 protected abstract URI getAuthenticationURI(Request request);
68
69 @Override
70 public Response.Listener getResponseListener()
71 {
72
73 return new AuthenticationListener();
74 }
75
76 private class AuthenticationListener extends BufferingResponseListener
77 {
78 private AuthenticationListener()
79 {
80 super(maxContentLength);
81 }
82
83 @Override
84 public void onComplete(Result result)
85 {
86 HttpRequest request = (HttpRequest)result.getRequest();
87 ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
88 if (result.isFailed())
89 {
90 Throwable failure = result.getFailure();
91 if (LOG.isDebugEnabled())
92 LOG.debug("Authentication challenge failed {}", failure);
93 forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
94 return;
95 }
96
97 HttpConversation conversation = request.getConversation();
98 if (conversation.getAttribute(AUTHENTICATION_ATTRIBUTE) != null)
99 {
100
101 if (LOG.isDebugEnabled())
102 LOG.debug("Bad credentials for {}", request);
103 forwardSuccessComplete(request, response);
104 return;
105 }
106
107 HttpHeader header = getAuthenticateHeader();
108 List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
109 if (headerInfos.isEmpty())
110 {
111 if (LOG.isDebugEnabled())
112 LOG.debug("Authentication challenge without {} header", header);
113 forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
114 return;
115 }
116
117 Authentication authentication = null;
118 Authentication.HeaderInfo headerInfo = null;
119 URI authURI = getAuthenticationURI(request);
120 if (authURI != null)
121 {
122 for (Authentication.HeaderInfo element : headerInfos)
123 {
124 authentication = client.getAuthenticationStore().findAuthentication(element.getType(), authURI, element.getRealm());
125 if (authentication != null)
126 {
127 headerInfo = element;
128 break;
129 }
130 }
131 }
132 if (authentication == null)
133 {
134 if (LOG.isDebugEnabled())
135 LOG.debug("No authentication available for {}", request);
136 forwardSuccessComplete(request, response);
137 return;
138 }
139
140 try
141 {
142 final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
143 if (LOG.isDebugEnabled())
144 LOG.debug("Authentication result {}", authnResult);
145 if (authnResult == null)
146 {
147 forwardSuccessComplete(request, response);
148 return;
149 }
150
151 conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true);
152
153 URI requestURI = request.getURI();
154 String path = null;
155 if (HttpMethod.CONNECT.is(request.getMethod()))
156 {
157 String uri = request.getScheme() + "://" + request.getHost();
158 int port = request.getPort();
159 if (port > 0)
160 uri += ":" + port;
161 requestURI = URI.create(uri);
162 path = request.getPath();
163 }
164 Request newRequest = client.copyRequest(request, requestURI);
165 if (path != null)
166 newRequest.path(path);
167
168 authnResult.apply(newRequest);
169
170 newRequest.onResponseSuccess(new Response.SuccessListener()
171 {
172 @Override
173 public void onSuccess(Response response)
174 {
175 client.getAuthenticationStore().addAuthenticationResult(authnResult);
176 }
177 });
178
179 Connection connection = (Connection)request.getAttributes().get(Connection.class.getName());
180 if (connection != null)
181 connection.send(newRequest, null);
182 else
183 newRequest.send(null);
184 }
185 catch (Throwable x)
186 {
187 if (LOG.isDebugEnabled())
188 LOG.debug("Authentication failed", x);
189 forwardFailureComplete(request, null, response, x);
190 }
191 }
192
193 private void forwardSuccessComplete(HttpRequest request, Response response)
194 {
195 HttpConversation conversation = request.getConversation();
196 conversation.updateResponseListeners(null);
197 notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
198 }
199
200 private void forwardFailureComplete(HttpRequest request, Throwable requestFailure, Response response, Throwable responseFailure)
201 {
202 HttpConversation conversation = request.getConversation();
203 conversation.updateResponseListeners(null);
204 notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
205 }
206
207 private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
208 {
209
210 List<Authentication.HeaderInfo> result = new ArrayList<>();
211 List<String> values = Collections.list(response.getHeaders().getValues(header.asString()));
212 for (String value : values)
213 {
214 Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
215 if (matcher.matches())
216 {
217 String type = matcher.group(1);
218 String realm = matcher.group(2);
219 String params = matcher.group(3);
220 Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
221 result.add(headerInfo);
222 }
223 }
224 return result;
225 }
226 }
227 }