Skip to content

Commit 9a609f3

Browse files
committed
Add twitter's authentication filter.
1 parent c42103e commit 9a609f3

3 files changed

Lines changed: 168 additions & 3 deletions

File tree

presto-main/src/main/java/com/facebook/presto/server/security/PasswordAuthenticator.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ public Principal authenticate(HttpServletRequest request)
5454

5555
int space = header.indexOf(' ');
5656
if ((space < 0) || !header.substring(0, space).equalsIgnoreCase("basic")) {
57-
if (securityConfig.getAllowByPass()) {
58-
return authenticatorManager.getAuthenticator().createAuthenticatedPrincipal("auth-by-pass", "_");
59-
}
6057
throw needAuthentication(null);
6158
}
6259
String credentials = decodeCredentials(header.substring(space + 1).trim());

presto-main/src/main/java/com/facebook/presto/server/security/ServerSecurityModule.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
import com.facebook.airlift.http.server.JsonWebTokenAuthenticator;
2020
import com.facebook.airlift.http.server.JsonWebTokenConfig;
2121
import com.facebook.airlift.http.server.KerberosConfig;
22+
import com.facebook.airlift.http.server.TheServlet;
2223
import com.facebook.presto.server.security.SecurityConfig.AuthenticationType;
2324
import com.google.inject.Binder;
2425
import com.google.inject.Scopes;
2526
import com.google.inject.multibindings.Multibinder;
2627

28+
import javax.servlet.Filter;
29+
2730
import java.util.List;
2831

2932
import static com.facebook.airlift.configuration.ConfigBinder.configBinder;
@@ -39,6 +42,9 @@ public class ServerSecurityModule
3942
@Override
4043
protected void setup(Binder binder)
4144
{
45+
newSetBinder(binder, Filter.class, TheServlet.class).addBinding()
46+
.to(TwitterAuthenticationFilter.class).in(Scopes.SINGLETON);
47+
4248
binder.bind(PasswordAuthenticatorManager.class).in(Scopes.SINGLETON);
4349
configBinder(binder).bindConfig(SecurityConfig.class);
4450

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.facebook.presto.server.security;
15+
16+
import com.facebook.airlift.http.server.AuthenticationException;
17+
import com.facebook.airlift.http.server.Authenticator;
18+
import com.facebook.airlift.log.Logger;
19+
import com.google.common.base.Joiner;
20+
import com.google.common.collect.ImmutableList;
21+
22+
import javax.inject.Inject;
23+
import javax.servlet.Filter;
24+
import javax.servlet.FilterChain;
25+
import javax.servlet.FilterConfig;
26+
import javax.servlet.ServletException;
27+
import javax.servlet.ServletRequest;
28+
import javax.servlet.ServletResponse;
29+
import javax.servlet.http.HttpServletRequest;
30+
import javax.servlet.http.HttpServletRequestWrapper;
31+
import javax.servlet.http.HttpServletResponse;
32+
33+
import java.io.IOException;
34+
import java.io.InputStream;
35+
import java.security.Principal;
36+
import java.util.LinkedHashSet;
37+
import java.util.List;
38+
import java.util.Set;
39+
40+
import static com.facebook.presto.client.PrestoHeaders.PRESTO_SOURCE;
41+
import static com.facebook.presto.client.PrestoHeaders.PRESTO_USER;
42+
import static com.google.common.io.ByteStreams.copy;
43+
import static com.google.common.io.ByteStreams.nullOutputStream;
44+
import static com.google.common.net.HttpHeaders.WWW_AUTHENTICATE;
45+
import static java.util.Objects.requireNonNull;
46+
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
47+
48+
public class TwitterAuthenticationFilter
49+
implements Filter
50+
{
51+
private static final Logger LOG = Logger.get(TwitterAuthenticationFilter.class);
52+
53+
private final List<Authenticator> authenticators;
54+
55+
private final String httpAuthenticationPathRegex;
56+
private final boolean allowByPass;
57+
private final String statementSourceByPassRegex;
58+
59+
@Inject
60+
public TwitterAuthenticationFilter(List<Authenticator> authenticators, SecurityConfig securityConfig)
61+
{
62+
this.authenticators = ImmutableList.copyOf(requireNonNull(authenticators, "authenticators is null"));
63+
this.httpAuthenticationPathRegex = requireNonNull(securityConfig.getHttpAuthenticationPathRegex(), "httpAuthenticationPathRegex is null");
64+
this.allowByPass = securityConfig.getAllowByPass();
65+
this.statementSourceByPassRegex = securityConfig.getStatementSourceByPassRegex();
66+
}
67+
68+
@Override
69+
public void init(FilterConfig filterConfig) {}
70+
71+
@Override
72+
public void destroy() {}
73+
74+
@Override
75+
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain nextFilter)
76+
throws IOException, ServletException
77+
{
78+
HttpServletRequest request = (HttpServletRequest) servletRequest;
79+
HttpServletResponse response = (HttpServletResponse) servletResponse;
80+
81+
// skip authentication if (not configured) or (non-secure and not match
82+
if (authenticators.isEmpty() || (!request.isSecure() && !request.getPathInfo().matches(httpAuthenticationPathRegex))) {
83+
nextFilter.doFilter(request, response);
84+
return;
85+
}
86+
87+
// try to authenticate, collecting errors and authentication headers
88+
Set<String> messages = new LinkedHashSet<>();
89+
Set<String> authenticateHeaders = new LinkedHashSet<>();
90+
91+
for (Authenticator authenticator : authenticators) {
92+
Principal principal;
93+
try {
94+
principal = authenticator.authenticate(request);
95+
}
96+
catch (AuthenticationException e) {
97+
if (e.getMessage() != null) {
98+
messages.add(e.getMessage());
99+
}
100+
e.getAuthenticateHeader().ifPresent(authenticateHeaders::add);
101+
continue;
102+
}
103+
104+
// authentication succeeded
105+
nextFilter.doFilter(withPrincipal(request, principal), response);
106+
return;
107+
}
108+
109+
//authentication bypassed
110+
if (allowByPass) {
111+
//TODO: remove this field once we enforced authentication 100%
112+
if (request.getMethod().equals("GET") && request.getPathInfo().startsWith("/v1/statement") ||
113+
(statementSourceByPassRegex != null
114+
&& request.getHeader(PRESTO_SOURCE) != null
115+
&& request.getHeader(PRESTO_SOURCE).matches(statementSourceByPassRegex))) {
116+
nextFilter.doFilter(request, response);
117+
LOG.debug("Authentication by passed from source: %s user: %s", request.getHeader(PRESTO_SOURCE), request.getHeader(PRESTO_USER));
118+
return;
119+
}
120+
}
121+
122+
// authentication failed
123+
skipRequestBody(request);
124+
125+
for (String value : authenticateHeaders) {
126+
response.addHeader(WWW_AUTHENTICATE, value);
127+
}
128+
129+
if (messages.isEmpty()) {
130+
messages.add("Unauthorized");
131+
}
132+
LOG.debug("Auth failed %s %s %s %s", request.getHeader(PRESTO_SOURCE), request.getHeader(PRESTO_USER), request.getPathInfo(), request.getQueryString());
133+
response.sendError(SC_UNAUTHORIZED, Joiner.on(" | ").join(messages));
134+
}
135+
136+
private static ServletRequest withPrincipal(HttpServletRequest request, Principal principal)
137+
{
138+
requireNonNull(principal, "principal is null");
139+
return new HttpServletRequestWrapper(request)
140+
{
141+
@Override
142+
public Principal getUserPrincipal()
143+
{
144+
return principal;
145+
}
146+
};
147+
}
148+
149+
private static void skipRequestBody(HttpServletRequest request)
150+
throws IOException
151+
{
152+
// If we send the challenge without consuming the body of the request,
153+
// the server will close the connection after sending the response.
154+
// The client may interpret this as a failed request and not resend the
155+
// request with the authentication header. We can avoid this behavior
156+
// in the client by reading and discarding the entire body of the
157+
// unauthenticated request before sending the response.
158+
try (InputStream inputStream = request.getInputStream()) {
159+
copy(inputStream, nullOutputStream());
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)