Skip to content

Commit 1502a7d

Browse files
[PIP 97] Update Authentication Interfaces to Include Async Authentication Methods (apache#12104)
Master Issue: apache#12105 ### Motivation As the first part of PIP-97, we need to update the interfaces. This PR is the only PR that will update interfaces. It should not introduce any breaking changes. ### Modifications #### AuthenticationProvider * Add `AuthenticationProvider#authenticateAsync`. Include a default implementation that calls the `authenticate` method. Note that current implementations should all be non-blocking, so there is no need to push the execution to a separate thread. * Deprecate `AuthenticationProvider#authenticate`. * Add `AuthenticationProvider#authenticateHttpRequestAsync`. This method is complicated. It is only called when using the SASL authentication provider (this is hard coded into the Pulsar code base). As such, I would argue that it is worth removing support for this unreachable method and then refactor the SASL authentication provider. I annotated this method with `@InterfaceStability.Unstable` and added details to the Javadoc in order to communicate the uncertainty of this method's future. I am happy to discuss this in more detail though. * Deprecate `AuthenticationProvider#authenticateHttpRequest`. #### AuthenticationState * Add `AuthenticationState#authenticateAsync`. Include a default implementation that calls the `authenticate` method and then performs a check to determine what result to return. Note that current implementations should all be non-blocking, so there is no need to push the execution to a separate thread. * Deprecate `AuthenticationState#authenticate`. The preferred method is `AuthenticationState#authenticateAsync`. * Deprecate `AuthenticationState#isComplete`. This method can be avoided by inferring authentication completeness from the result of `AuthenticationState#authenticateAsync`. When the result is `null`, auth is complete. When it is not `null`, auth is not complete. Since the result of the `authenticateAsync` method is the body delivered to the client, this seems like a reasonable abstraction to make. As a consequence, the `AuthenticationState` is simpler and also avoids certain thread safety issues that might arise when calling `isComplete` from a different thread. #### AuthenticationDataSource * Deprecate `AuthenticationDataSource#authenticate`. This method is not called by the Pulsar authentication framework. This needs to be deprecated to prevent confusion for end users seeking to extend the authentication framework. There is no need for an async version of this method. ### Verifying this change These changes only affect the interfaces. I will need to add tests to verify the correctness of the default implementations in this PR. ### Does this pull request potentially affect one of the following parts: Yes, it affects the public API. That is why it has a PIP. ### Documentation I've updated the Javadocs. There is not any current documentation on implementing your own authentication provider, so I think updating Javadocs is sufficient documentation, for now. (cherry picked from commit 5868025)
1 parent b47d8c6 commit 1502a7d

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationDataSource.java

+3
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ default String getCommandData() {
102102
/**
103103
* Evaluate and challenge the data that passed in, and return processed data back.
104104
* It is used for mutual authentication like SASL.
105+
* NOTE: this method is not called by the Pulsar authentication framework.
106+
* @deprecated use {@link AuthenticationProvider} or {@link AuthenticationState}.
105107
*/
108+
@Deprecated
106109
default AuthData authenticate(AuthData data) throws AuthenticationException {
107110
throw new AuthenticationException("Not supported");
108111
}

pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProvider.java

+56
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@
2121
import java.io.Closeable;
2222
import java.io.IOException;
2323
import java.net.SocketAddress;
24+
import java.util.concurrent.CompletableFuture;
2425
import javax.naming.AuthenticationException;
2526
import javax.net.ssl.SSLSession;
2627
import javax.servlet.http.HttpServletRequest;
2728
import javax.servlet.http.HttpServletResponse;
2829
import org.apache.pulsar.broker.ServiceConfiguration;
2930
import org.apache.pulsar.common.api.AuthData;
31+
import org.apache.pulsar.common.classification.InterfaceStability;
32+
import org.apache.pulsar.common.util.FutureUtil;
3033

3134
/**
3235
* Provider of authentication mechanism.
@@ -48,6 +51,29 @@ public interface AuthenticationProvider extends Closeable {
4851
*/
4952
String getAuthMethodName();
5053

54+
/**
55+
* Validate the authentication for the given credentials with the specified authentication data.
56+
* This method is useful in one stage authentication, if you're not doing one stage or if you're providing
57+
* your own state implementation for one stage authentication, it should return a failed future.
58+
*
59+
* <p>Warning: the calling thread is an IO thread. Any implementation that relies on blocking behavior
60+
* must ensure that the execution is completed using a separate thread pool to ensure IO threads
61+
* are never blocked.</p>
62+
*
63+
* @param authData authentication data generated while initiating a connection. There are several types,
64+
* including, but not strictly limited to, {@link AuthenticationDataHttp},
65+
* {@link AuthenticationDataHttps}, and {@link AuthenticationDataCommand}.
66+
* @return A completed future with the "role" string for the authenticated connection, if authentication is
67+
* successful, or a failed future if the authData is not valid.
68+
*/
69+
default CompletableFuture<String> authenticateAsync(AuthenticationDataSource authData) {
70+
try {
71+
return CompletableFuture.completedFuture(this.authenticate(authData));
72+
} catch (AuthenticationException e) {
73+
return FutureUtil.failedFuture(e);
74+
}
75+
}
76+
5177
/**
5278
* Validate the authentication for the given credentials with the specified authentication data.
5379
* This method is useful in one stage authn, if you're not doing one stage or if you're providing
@@ -58,7 +84,9 @@ public interface AuthenticationProvider extends Closeable {
5884
* @return the "role" string for the authenticated connection, if the authentication was successful
5985
* @throws AuthenticationException
6086
* if the credentials are not valid
87+
* @deprecated use and implement {@link AuthenticationProvider#authenticateAsync(AuthenticationDataSource)} instead.
6188
*/
89+
@Deprecated
6290
default String authenticate(AuthenticationDataSource authData) throws AuthenticationException {
6391
throw new AuthenticationException("Not supported");
6492
}
@@ -73,10 +101,38 @@ default AuthenticationState newAuthState(AuthData authData,
73101
return new OneStageAuthenticationState(authData, remoteAddress, sslSession, this);
74102
}
75103

104+
/**
105+
* Validate the authentication for the given credentials with the specified authentication data.
106+
*
107+
* <p>Warning: the calling thread is an IO thread. Any implementations that rely on blocking behavior
108+
* must ensure that the execution is completed on using a separate thread pool to ensure IO threads
109+
* are never blocked.</p>
110+
*
111+
* <p>Note: this method is marked as unstable because the Pulsar code base only calls it for the
112+
* Pulsar Broker Auth SASL plugin. All non SASL HTTP requests are authenticated using the
113+
* {@link AuthenticationProvider#authenticateAsync(AuthenticationDataSource)} method. As such,
114+
* this method might be removed in favor of the SASL provider implementing the
115+
* {@link AuthenticationProvider#authenticateAsync(AuthenticationDataSource)} method.</p>
116+
*
117+
* @return Set response, according to passed in request.
118+
* and return whether we should do following chain.doFilter or not.
119+
*/
120+
@InterfaceStability.Unstable
121+
default CompletableFuture<Boolean> authenticateHttpRequestAsync(HttpServletRequest request,
122+
HttpServletResponse response) {
123+
try {
124+
return CompletableFuture.completedFuture(this.authenticateHttpRequest(request, response));
125+
} catch (Exception e) {
126+
return FutureUtil.failedFuture(e);
127+
}
128+
}
129+
76130
/**
77131
* Set response, according to passed in request.
78132
* and return whether we should do following chain.doFilter or not.
133+
* @deprecated use and implement {@link AuthenticationProvider#authenticateHttpRequestAsync} instead.
79134
*/
135+
@Deprecated
80136
default boolean authenticateHttpRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
81137
throw new AuthenticationException("Not supported");
82138
}

pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationState.java

+36
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
import javax.naming.AuthenticationException;
2323
import org.apache.pulsar.common.api.AuthData;
24+
import org.apache.pulsar.common.util.FutureUtil;
25+
26+
import java.util.concurrent.CompletableFuture;
2427

2528
/**
2629
* Interface for authentication state.
@@ -38,17 +41,50 @@ public interface AuthenticationState {
3841

3942
/**
4043
* Challenge passed in auth data and get response data.
44+
* @deprecated use and implement {@link AuthenticationState#authenticateAsync(AuthData)} instead.
4145
*/
46+
@Deprecated
4247
AuthData authenticate(AuthData authData) throws AuthenticationException;
4348

49+
/**
50+
* Challenge passed in auth data. If authentication is complete after the execution of this method, return null.
51+
* Otherwise, return response data to be sent to the client.
52+
*
53+
* <p>Note: the implementation of {@link AuthenticationState#authenticate(AuthData)} converted a null result into a
54+
* zero length byte array when {@link AuthenticationState#isComplete()} returned false after authentication. In
55+
* order to simplify this interface, the determination of whether to send a challenge back to the client is only
56+
* based on the result of this method. In order to maintain backwards compatibility, the default implementation of
57+
* this method calls {@link AuthenticationState#isComplete()} and returns a result compliant with the new
58+
* paradigm.</p>
59+
*/
60+
default CompletableFuture<AuthData> authenticateAsync(AuthData authData) {
61+
try {
62+
AuthData result = this.authenticate(authData);
63+
if (isComplete()) {
64+
return CompletableFuture.completedFuture(null);
65+
} else {
66+
return result != null
67+
? CompletableFuture.completedFuture(result)
68+
: CompletableFuture.completedFuture(AuthData.of(new byte[0]));
69+
}
70+
} catch (Exception e) {
71+
return FutureUtil.failedFuture(e);
72+
}
73+
}
74+
4475
/**
4576
* Return AuthenticationDataSource.
4677
*/
4778
AuthenticationDataSource getAuthDataSource();
4879

4980
/**
5081
* Whether the authentication is completed or not.
82+
* @deprecated this method's logic is captured by the result of
83+
* {@link AuthenticationState#authenticateAsync(AuthData)}. When the result is a {@link CompletableFuture} with a
84+
* null result, authentication is complete. When the result is a {@link CompletableFuture} with a nonnull result,
85+
* authentication is incomplete and requires an auth challenge.
5186
*/
87+
@Deprecated
5288
boolean isComplete();
5389

5490
/**

0 commit comments

Comments
 (0)