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.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
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
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
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 }