Skip to content

Commit a1f44c1

Browse files
committed
Add AuthenticatorNotApplicableException in presto-spi
1 parent b0f44f0 commit a1f44c1

File tree

6 files changed

+128
-41
lines changed

6 files changed

+128
-41
lines changed

presto-docs/src/main/sphinx/develop/presto-authenticator.rst

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,36 @@ Implementation
1212
``PrestoAuthenticator`` instance. It also defines the name of this
1313
authenticator which is used by the administrator in a Presto configuration.
1414

15-
``PrestoAuthenticator`` contains a single method, ``createAuthenticatedPrincipal()``,
16-
that validates the request and returns a ``Principal``, which is then
15+
``PrestoAuthenticator`` contains a single method, ``createAuthenticatedPrincipal(Map<String, List<String>> headers)``,
16+
that validates the request headers and returns a ``Principal``, which is then
1717
authorized by the :doc:`system-access-control`.
1818

1919
The implementation of ``PrestoAuthenticatorFactory`` must be wrapped
2020
as a plugin and installed on the Presto cluster.
2121

22+
Error Handling
23+
--------------
24+
25+
The ``createAuthenticatedPrincipal(Map<String, List<String>> headers)`` method can throw two types of exceptions,
26+
depending on the authentication outcome:
27+
28+
* ``AuthenticatorNotApplicableException``:
29+
30+
Thrown when the required authentication header is missing or invalid. This signals
31+
to Presto that the current authentication method is not applicable, so it should
32+
skip this authenticator and try the next configured one. The exception message is
33+
not returned to the user, since authentication was never intended for this request.
34+
35+
* ``AccessDeniedException``:
36+
37+
Thrown when the required header is present but authentication fails. In this case,
38+
Presto will still try the next configured authenticator but the error message is
39+
passed back to the user, indicating that the authentication attempt was valid but
40+
unsuccessful.
41+
42+
This distinction ensures that Presto can properly chain multiple authenticators
43+
while providing meaningful feedback to the user only when appropriate.
44+
2245
Configuration
2346
-------------
2447

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
import com.facebook.airlift.http.server.AuthenticationException;
1717
import com.facebook.airlift.http.server.Authenticator;
18+
import com.facebook.airlift.log.Logger;
1819
import com.facebook.presto.spi.security.AccessDeniedException;
20+
import com.facebook.presto.spi.security.AuthenticatorNotApplicableException;
1921
import jakarta.inject.Inject;
2022
import jakarta.servlet.http.HttpServletRequest;
2123

@@ -30,6 +32,8 @@
3032
public class CustomPrestoAuthenticator
3133
implements Authenticator
3234
{
35+
private static final Logger log = Logger.get(CustomPrestoAuthenticator.class);
36+
3337
private PrestoAuthenticatorManager authenticatorManager;
3438

3539
@Inject
@@ -49,11 +53,21 @@ public Principal authenticate(HttpServletRequest request)
4953
// Passing the header map to the authenticator (instead of HttpServletRequest)
5054
return authenticatorManager.getAuthenticator().createAuthenticatedPrincipal(headers);
5155
}
56+
catch (AuthenticatorNotApplicableException e) {
57+
// Presto will gracefully handle this exception and will not propagate it back to the client
58+
log.debug(e, e.getMessage());
59+
throw needAuthentication();
60+
}
5261
catch (AccessDeniedException e) {
5362
throw new AuthenticationException(e.getMessage());
5463
}
5564
}
5665

66+
private static AuthenticationException needAuthentication()
67+
{
68+
return new AuthenticationException(null);
69+
}
70+
5771
// Utility method to extract headers from HttpServletRequest
5872
private Map<String, List<String>> getHeadersMap(HttpServletRequest request)
5973
{

presto-main/src/test/java/com/facebook/presto/server/security/TestCustomPrestoAuthenticator.java

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
*/
1414
package com.facebook.presto.server.security;
1515

16+
import com.facebook.airlift.http.server.AuthenticationException;
1617
import com.facebook.presto.security.BasicPrincipal;
1718
import com.facebook.presto.server.MockHttpServletRequest;
1819
import com.facebook.presto.spi.security.AccessDeniedException;
20+
import com.facebook.presto.spi.security.AuthenticatorNotApplicableException;
1921
import com.facebook.presto.spi.security.PrestoAuthenticator;
2022
import com.facebook.presto.spi.security.PrestoAuthenticatorFactory;
2123
import com.google.common.collect.ImmutableList;
@@ -25,20 +27,16 @@
2527
import org.testng.annotations.Test;
2628

2729
import java.security.Principal;
28-
import java.util.List;
2930
import java.util.Map;
30-
import java.util.Optional;
3131

32-
import static com.google.common.collect.ImmutableMap.toImmutableMap;
33-
import static java.util.Collections.list;
3432
import static java.util.Objects.requireNonNull;
33+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3534
import static org.testng.Assert.assertEquals;
36-
import static org.testng.Assert.assertFalse;
37-
import static org.testng.Assert.assertTrue;
3835

3936
public class TestCustomPrestoAuthenticator
4037
{
4138
private static final String TEST_HEADER = "test_header";
39+
private static final String TEST_INVALID_HEADER = "test_invalid_header";
4240
private static final String TEST_HEADER_VALID_VALUE = "VALID";
4341
private static final String TEST_HEADER_INVALID_VALUE = "INVALID";
4442
private static final String TEST_FACTORY = "test_factory";
@@ -47,15 +45,9 @@ public class TestCustomPrestoAuthenticator
4745

4846
@Test
4947
public void testPrestoAuthenticator()
48+
throws Exception
5049
{
51-
SecurityConfig mockSecurityConfig = new SecurityConfig();
52-
mockSecurityConfig.setAuthenticationTypes(ImmutableList.of(SecurityConfig.AuthenticationType.CUSTOM));
53-
PrestoAuthenticatorManager prestoAuthenticatorManager = new PrestoAuthenticatorManager(mockSecurityConfig);
54-
// Add Test Presto Authenticator Factory
55-
prestoAuthenticatorManager.addPrestoAuthenticatorFactory(
56-
new TestingPrestoAuthenticatorFactory(
57-
TEST_FACTORY,
58-
TEST_HEADER_VALID_VALUE));
50+
PrestoAuthenticatorManager prestoAuthenticatorManager = getPrestoAuthenticatorManager();
5951

6052
prestoAuthenticatorManager.loadAuthenticator(TEST_FACTORY);
6153

@@ -65,52 +57,77 @@ public void testPrestoAuthenticator()
6557
TEST_REMOTE_ADDRESS,
6658
ImmutableMap.of());
6759

68-
Optional<Principal> principal = checkAuthentication(prestoAuthenticatorManager.getAuthenticator(), request);
69-
assertTrue(principal.isPresent());
70-
assertEquals(principal.get().getName(), TEST_USER);
60+
CustomPrestoAuthenticator customPrestoAuthenticator = new CustomPrestoAuthenticator(prestoAuthenticatorManager);
61+
Principal principal = customPrestoAuthenticator.authenticate(request);
62+
63+
assertEquals(principal.getName(), TEST_USER);
64+
}
65+
66+
@Test(expectedExceptions = AuthenticationException.class, expectedExceptionsMessageRegExp = "Access Denied: Authentication Failed!")
67+
public void testPrestoAuthenticatorFailedAuthentication()
68+
throws AuthenticationException
69+
{
70+
PrestoAuthenticatorManager prestoAuthenticatorManager = getPrestoAuthenticatorManager();
71+
72+
prestoAuthenticatorManager.loadAuthenticator(TEST_FACTORY);
7173

7274
// Test failed authentication
73-
request = new MockHttpServletRequest(
75+
HttpServletRequest request = new MockHttpServletRequest(
7476
ImmutableListMultimap.of(TEST_HEADER, TEST_HEADER_INVALID_VALUE + ":" + TEST_USER),
7577
TEST_REMOTE_ADDRESS,
7678
ImmutableMap.of());
7779

78-
principal = checkAuthentication(prestoAuthenticatorManager.getAuthenticator(), request);
79-
assertFalse(principal.isPresent());
80+
CustomPrestoAuthenticator customPrestoAuthenticator = new CustomPrestoAuthenticator(prestoAuthenticatorManager);
81+
customPrestoAuthenticator.authenticate(request);
8082
}
8183

82-
private Optional<Principal> checkAuthentication(PrestoAuthenticator authenticator, HttpServletRequest request)
84+
@Test
85+
public void testPrestoAuthenticatorNotApplicable()
8386
{
84-
try {
85-
// Converting HttpServletRequest to Map<String, String>
86-
Map<String, List<String>> headers = getHeadersMap(request);
87+
PrestoAuthenticatorManager prestoAuthenticatorManager = getPrestoAuthenticatorManager();
8788

88-
// Passing the headers Map to the authenticator
89-
return Optional.of(authenticator.createAuthenticatedPrincipal(headers));
90-
}
91-
catch (AccessDeniedException e) {
92-
return Optional.empty();
93-
}
89+
prestoAuthenticatorManager.loadAuthenticator(TEST_FACTORY);
90+
91+
// Test invalid authenticator
92+
HttpServletRequest request = new MockHttpServletRequest(
93+
ImmutableListMultimap.of(TEST_INVALID_HEADER, TEST_HEADER_VALID_VALUE + ":" + TEST_USER),
94+
TEST_REMOTE_ADDRESS,
95+
ImmutableMap.of());
96+
97+
CustomPrestoAuthenticator customPrestoAuthenticator = new CustomPrestoAuthenticator(prestoAuthenticatorManager);
98+
99+
assertThatThrownBy(() -> customPrestoAuthenticator.authenticate(request))
100+
.isInstanceOf(AuthenticationException.class)
101+
.hasMessage(null);
94102
}
95103

96-
private Map<String, List<String>> getHeadersMap(HttpServletRequest request)
104+
private static PrestoAuthenticatorManager getPrestoAuthenticatorManager()
97105
{
98-
return list(request.getHeaderNames())
99-
.stream()
100-
.collect(toImmutableMap(
101-
headerName -> headerName,
102-
headerName -> list(request.getHeaders(headerName))));
106+
SecurityConfig mockSecurityConfig = new SecurityConfig();
107+
mockSecurityConfig.setAuthenticationTypes(ImmutableList.of(SecurityConfig.AuthenticationType.CUSTOM));
108+
PrestoAuthenticatorManager prestoAuthenticatorManager = new PrestoAuthenticatorManager(mockSecurityConfig);
109+
110+
// Add Test Presto Authenticator Factory
111+
prestoAuthenticatorManager.addPrestoAuthenticatorFactory(
112+
new TestingPrestoAuthenticatorFactory(
113+
TEST_FACTORY,
114+
TEST_HEADER,
115+
TEST_HEADER_VALID_VALUE));
116+
117+
return prestoAuthenticatorManager;
103118
}
104119

105120
private static class TestingPrestoAuthenticatorFactory
106121
implements PrestoAuthenticatorFactory
107122
{
108123
private final String name;
124+
private final String validHeaderName;
109125
private final String validHeaderValue;
110126

111-
TestingPrestoAuthenticatorFactory(String name, String validHeaderValue)
127+
TestingPrestoAuthenticatorFactory(String name, String validHeaderName, String validHeaderValue)
112128
{
113129
this.name = requireNonNull(name, "name is null");
130+
this.validHeaderName = requireNonNull(validHeaderName, "validHeaderName is null");
114131
this.validHeaderValue = requireNonNull(validHeaderValue, "validHeaderValue is null");
115132
}
116133

@@ -124,8 +141,12 @@ public String getName()
124141
public PrestoAuthenticator create(Map<String, String> config)
125142
{
126143
return (headers) -> {
127-
// TEST_HEADER will have value of the form PART1:PART2
128-
String[] header = headers.get(TEST_HEADER).get(0).split(":");
144+
if (!headers.containsKey(this.validHeaderName)) {
145+
throw new AuthenticatorNotApplicableException("Invalid authenticator: required headers are missing");
146+
}
147+
148+
// HEADER will have value of the form PART1:PART2
149+
String[] header = headers.get(this.validHeaderName).get(0).split(":");
129150

130151
if (header[0].equals(this.validHeaderValue)) {
131152
return new BasicPrincipal(header[1]);

presto-spi/src/main/java/com/facebook/presto/spi/StandardErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ public enum StandardErrorCode
150150
HEADER_MODIFICATION_ATTEMPT(0x0002_0015, INTERNAL_ERROR),
151151
DUPLICATE_FUNCTION_ERROR(0x0002_0016, INTERNAL_ERROR),
152152
MEMORY_ARBITRATION_FAILURE(0x0002_0017, INSUFFICIENT_RESOURCES),
153+
AUTHENTICATOR_NOT_APPLICABLE(0x0002_0018, INTERNAL_ERROR),
153154
/**/;
154155

155156
// Error code range 0x0003 is reserved for Presto-on-Spark
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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.spi.security;
15+
16+
import com.facebook.presto.spi.PrestoException;
17+
18+
import static com.facebook.presto.spi.StandardErrorCode.AUTHENTICATOR_NOT_APPLICABLE;
19+
20+
public class AuthenticatorNotApplicableException
21+
extends PrestoException
22+
{
23+
public AuthenticatorNotApplicableException(String message)
24+
{
25+
super(AUTHENTICATOR_NOT_APPLICABLE, message);
26+
}
27+
}

presto-spi/src/main/java/com/facebook/presto/spi/security/PrestoAuthenticator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public interface PrestoAuthenticator
2424
*
2525
* @return the authenticated Principal
2626
* @throws AccessDeniedException if not allowed
27+
* @throws AuthenticatorNotApplicableException if authenticator is not valid
2728
*/
2829
Principal createAuthenticatedPrincipal(Map<String, List<String>> headers);
2930
}

0 commit comments

Comments
 (0)