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.HttpField;
36 import org.eclipse.jetty.http.HttpHeader;
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 = 16*1024;
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
46 private final HttpClient client;
47 private final int maxContentLength;
48 private final ResponseNotifier notifier;
49
50 protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
51 {
52 this.client = client;
53 this.maxContentLength = maxContentLength;
54 this.notifier = new ResponseNotifier();
55 }
56
57 protected HttpClient getHttpClient()
58 {
59 return client;
60 }
61
62 protected abstract HttpHeader getAuthenticateHeader();
63
64 protected abstract HttpHeader getAuthorizationHeader();
65
66 protected abstract URI getAuthenticationURI(Request request);
67
68 protected abstract String getAuthenticationAttribute();
69
70 @Override
71 public Response.Listener getResponseListener()
72 {
73
74 return new AuthenticationListener();
75 }
76
77 private class AuthenticationListener extends BufferingResponseListener
78 {
79 private AuthenticationListener()
80 {
81 super(maxContentLength);
82 }
83
84 @Override
85 public void onComplete(Result result)
86 {
87 HttpRequest request = (HttpRequest)result.getRequest();
88 ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
89 if (result.isFailed())
90 {
91 if (LOG.isDebugEnabled())
92 LOG.debug("Authentication challenge failed {}", result.getFailure());
93 forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
94 return;
95 }
96
97 String authenticationAttribute = getAuthenticationAttribute();
98 HttpConversation conversation = request.getConversation();
99 if (conversation.getAttribute(authenticationAttribute) != null)
100 {
101
102 if (LOG.isDebugEnabled())
103 LOG.debug("Bad credentials for {}", request);
104 forwardSuccessComplete(request, response);
105 return;
106 }
107
108 HttpHeader header = getAuthenticateHeader();
109 List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
110 if (headerInfos.isEmpty())
111 {
112 if (LOG.isDebugEnabled())
113 LOG.debug("Authentication challenge without {} header", header);
114 forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
115 return;
116 }
117
118 Authentication authentication = null;
119 Authentication.HeaderInfo headerInfo = null;
120 URI authURI = resolveURI(request, getAuthenticationURI(request));
121 if (authURI != null)
122 {
123 for (Authentication.HeaderInfo element : headerInfos)
124 {
125 authentication = client.getAuthenticationStore().findAuthentication(element.getType(), authURI, element.getRealm());
126 if (authentication != null)
127 {
128 headerInfo = element;
129 break;
130 }
131 }
132 }
133 if (authentication == null)
134 {
135 if (LOG.isDebugEnabled())
136 LOG.debug("No authentication available for {}", request);
137 forwardSuccessComplete(request, response);
138 return;
139 }
140
141 try
142 {
143 final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
144 if (LOG.isDebugEnabled())
145 LOG.debug("Authentication result {}", authnResult);
146 if (authnResult == null)
147 {
148 forwardSuccessComplete(request, response);
149 return;
150 }
151
152 conversation.setAttribute(authenticationAttribute, true);
153
154 URI requestURI = request.getURI();
155 String path = null;
156 if (requestURI == null)
157 {
158 requestURI = resolveURI(request, null);
159 path = request.getPath();
160 }
161 Request newRequest = client.copyRequest(request, requestURI);
162 if (path != null)
163 newRequest.path(path);
164
165 authnResult.apply(newRequest);
166
167 copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION);
168 copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION);
169
170 newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult));
171
172 Connection connection = (Connection)request.getAttributes().get(Connection.class.getName());
173 if (connection != null)
174 connection.send(newRequest, null);
175 else
176 newRequest.send(null);
177 }
178 catch (Throwable x)
179 {
180 if (LOG.isDebugEnabled())
181 LOG.debug("Authentication failed", x);
182 forwardFailureComplete(request, null, response, x);
183 }
184 }
185
186 private URI resolveURI(HttpRequest request, URI uri)
187 {
188 if (uri != null)
189 return uri;
190 String target = request.getScheme() + "://" + request.getHost();
191 int port = request.getPort();
192 if (port > 0)
193 target += ":" + port;
194 return URI.create(target);
195 }
196
197 private void copyIfAbsent(HttpRequest oldRequest, Request newRequest, HttpHeader header)
198 {
199 HttpField field = oldRequest.getHeaders().getField(header);
200 if (field != null && !newRequest.getHeaders().contains(header))
201 newRequest.getHeaders().put(field);
202 }
203
204 private void forwardSuccessComplete(HttpRequest request, Response response)
205 {
206 HttpConversation conversation = request.getConversation();
207 conversation.updateResponseListeners(null);
208 notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
209 }
210
211 private void forwardFailureComplete(HttpRequest request, Throwable requestFailure, Response response, Throwable responseFailure)
212 {
213 HttpConversation conversation = request.getConversation();
214 conversation.updateResponseListeners(null);
215 List<Response.ResponseListener> responseListeners = conversation.getResponseListeners();
216 if (responseFailure == null)
217 notifier.forwardSuccess(responseListeners, response);
218 else
219 notifier.forwardFailure(responseListeners, response, responseFailure);
220 notifier.notifyComplete(responseListeners, new Result(request, requestFailure, response, responseFailure));
221 }
222
223 private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
224 {
225
226 List<Authentication.HeaderInfo> result = new ArrayList<>();
227 List<String> values = Collections.list(response.getHeaders().getValues(header.asString()));
228 for (String value : values)
229 {
230 Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
231 if (matcher.matches())
232 {
233 String type = matcher.group(1);
234 String realm = matcher.group(2);
235 String params = matcher.group(3);
236 Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
237 result.add(headerInfo);
238 }
239 }
240 return result;
241 }
242 }
243 }