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 = 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 String uri = request.getScheme() + "://" + request.getHost();
159 int port = request.getPort();
160 if (port > 0)
161 uri += ":" + port;
162 requestURI = URI.create(uri);
163 path = request.getPath();
164 }
165 Request newRequest = client.copyRequest(request, requestURI);
166 if (path != null)
167 newRequest.path(path);
168
169 authnResult.apply(newRequest);
170
171 copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION);
172 copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION);
173
174 newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult));
175
176 Connection connection = (Connection)request.getAttributes().get(Connection.class.getName());
177 if (connection != null)
178 connection.send(newRequest, null);
179 else
180 newRequest.send(null);
181 }
182 catch (Throwable x)
183 {
184 if (LOG.isDebugEnabled())
185 LOG.debug("Authentication failed", x);
186 forwardFailureComplete(request, null, response, x);
187 }
188 }
189
190 private void copyIfAbsent(HttpRequest oldRequest, Request newRequest, HttpHeader header)
191 {
192 HttpField field = oldRequest.getHeaders().getField(header);
193 if (field != null && !newRequest.getHeaders().contains(header))
194 newRequest.getHeaders().put(field);
195 }
196
197 private void forwardSuccessComplete(HttpRequest request, Response response)
198 {
199 HttpConversation conversation = request.getConversation();
200 conversation.updateResponseListeners(null);
201 notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
202 }
203
204 private void forwardFailureComplete(HttpRequest request, Throwable requestFailure, Response response, Throwable responseFailure)
205 {
206 HttpConversation conversation = request.getConversation();
207 conversation.updateResponseListeners(null);
208 List<Response.ResponseListener> responseListeners = conversation.getResponseListeners();
209 if (responseFailure == null)
210 notifier.forwardSuccess(responseListeners, response);
211 else
212 notifier.forwardFailure(responseListeners, response, responseFailure);
213 notifier.notifyComplete(responseListeners, new Result(request, requestFailure, response, responseFailure));
214 }
215
216 private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
217 {
218
219 List<Authentication.HeaderInfo> result = new ArrayList<>();
220 List<String> values = Collections.list(response.getHeaders().getValues(header.asString()));
221 for (String value : values)
222 {
223 Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
224 if (matcher.matches())
225 {
226 String type = matcher.group(1);
227 String realm = matcher.group(2);
228 String params = matcher.group(3);
229 Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
230 result.add(headerInfo);
231 }
232 }
233 return result;
234 }
235 }
236 }