Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIP 97: Asynchronous Authentication Provider #12105

Open
michaeljmarshall opened this issue Sep 20, 2021 · 6 comments
Open

PIP 97: Asynchronous Authentication Provider #12105

michaeljmarshall opened this issue Sep 20, 2021 · 6 comments
Assignees
Labels

Comments

@michaeljmarshall
Copy link
Member

michaeljmarshall commented Sep 20, 2021

Motivation

Pulsar's current AuthenticationProvider interface only exposes synchronous methods for authenticating a connection. To date, this has been sufficient because we do not have any providers that rely on network calls. However, in looking at the OAuth2.0 spec, there are some cases where network calls are necessary to verify a token. As a prerequisite to adding an OAuth2.0 AuthenticationProvider (or any other provider that relies on possibly blocking behavior), I propose the addition of asynchronous methods to the AuthenticationProvider interface and the AuthenticationState interface.

Goal

Update authentication provider framework to support more authentication protocols (like OAuth2.0 or OpenID Connect) while providing a way to ensure that implementations do not block IO threads.

Target version: 2.10 2.12

API Changes

In order to prevent confusion, the old authenticate methods are deprecated. Three interfaces will need to be updated for this PIP: AuthenticationProvider, AuthenticationState, and AuthenticationDataSource.

The API changes are shown in this PR: #12104

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.

Implementation

In addition to updating the above interfaces, we will need to update the consumers of those interfaces. The consumers (and transitive consumers) are currently the following classes: AuthenticationService, AuthenticationFilter, WebSocketWebResource, AbstractWebSocketHandler, ServerCnx, ProxyConnection, and ServerConnection.

AuthenticationService

It should be trivial to update this class, as the authenticateHttpRequest method's return type will be updated to return a CompletableFuture<String> instead of a String.

WebSocketWebResource

This class will require an update to the several endpoints so that they can return asynchronous objects. There is already a pattern in Pulsar for handling this kind of response using @Suspended final AsyncResponse asyncResponse, so I expect the implementation to be trivial.

AbstractWebSocketHandler

This class has a checkAuth method. The creation of a subscription is currently synchronous. I'm not sure how to implement asynchronous responses in websocket handlers. It looks like it should be possible from the following doc: https://webtide.com/jetty-9-updated-websocket-api/. I will need to do a little research to complete this implementation.

AuthenticationFilter

This class is an implementation of the javax.servlet.Filter interface. We will need to follow the servlet's paradigm for ensuring that asynchronous results are properly handled when determining whether to call chain.doFilter after the asynchronous authentication is complete. I will need to do a little research to complete this implementation.

ServerConnection

The ServerConnection class is part of the DiscoveryService. Instead of updating the implementation, I think it makes more sense to remove the module altogether. I started a thread about this on the mailing list here: https://lists.apache.org/x/thread.html/rc8b796dda2bc1b022e855c7368d832b570967cb1ef29e9cd18e04e97@%3Cdev.pulsar.apache.org%3E. If we do not remove the module, this class will need to be updated as well.

ServerCnx and ProxyConnection

Both of these classes rely on netty for method synchronization. Since authentication can happen in another thread, we will need to update these class's state to State.Connecting before calling the authenticateAsync methods. This will prevent a potential denial of service issue. Then, when the authenticateAsync method is called, we will need to register a callback to update the state of the connection class. I believe we will want to schedule this using each class's executor, as follows: this.ctx().executor(). By using the class's executor, we won't need to update any values to volatile. I'd appreciate confirmation that this interpretation of netty's method is true.

Implementations of Interfaces

Once the above changes are implemented, it will make sense to update our implementations of these interfaces to ensure that they implement the new methods.

Tests

All of these class changes will have associated test changes.

Reject Alternatives

On the initial mailing list thread (link in references, below), it was suggested that adding a new async interface would be easier. That would mean that the old, synchronous interface, AuthenticationProvider would be deprecated, and we would start with a fresh, new interface. In considering this solution, I decided it was suboptimal because it would lead to the creation of many new translation classes as well as duplicate fields. One such class was described on the mailing list:

class AsyncAuthBridge implements AsyncAuthenticationProvider {
      AuthenticationProvider delegate;
      Executor e;

     CompletableFuture<Boolean> authenticateAsync(AuthenticationDataSource authData) {
         CompletableFuture<Boolean> f = new CompletableFuture<>();
         e.execute(() -> {
               f.complete(delegate.authenticate(authData));
         });
         return f;
    }
}

This implementation makes sense, but since existing implementations of the AuthenticationProvider interface and the AuthenticationState interface are synchronous and should be non-blocking, I don't think we need to worry about pushing their execution to a separate thread.

Further, if we created a new interfaces named AsyncAuthenticationProvider and AsyncAuthenticationState, I think we'd end up duplicating most of the interface's fields/methods in the original synchronous interfaces because the same logic is still required. We are only changing implementation details by making the authenticate methods run asynchronously.

References

This addition was first discussed on the dev mailing list here: https://lists.apache.org/x/thread.html/r6c2522ca62242109758586696261cb1f4b4ce8e94ae593fda6e97b99@%3Cdev.pulsar.apache.org%3E.

@eolivelli
Copy link
Contributor

@merlimat FYI

@michaeljmarshall michaeljmarshall changed the title PIP 97: Asynchronous Authentication PIP 97: Asynchronous Authentication Provider Sep 24, 2021
codelipenghui pushed a commit that referenced this issue Nov 10, 2021
…tion Methods (#12104)

Master Issue: #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.
eolivelli pushed a commit to eolivelli/pulsar that referenced this issue Nov 29, 2021
…tion 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.
michaeljmarshall added a commit that referenced this issue Feb 23, 2022
…thentication Methods (#12104)" (#14424)

This reverts commit 5868025.

Master Issue: #12105

### Motivation

Because the PIP is not completely implemented, we should not publish the interface changes in 2.10.

### Modifications

* Revert the core commit for PIP 97.

### Verifying this change

This change is trivial, because the original change was completely backwards compatible.

### Documentation

- [x] `no-need-doc`
@github-actions
Copy link

github-actions bot commented Mar 1, 2022

The issue had no activity for 30 days, mark with Stale label.

@tisonkun
Copy link
Member

tisonkun commented Dec 7, 2022

@michaeljmarshall this PIP said target to 2.10. Shall we close it as delivered?

@michaeljmarshall
Copy link
Member Author

@tisonkun - this PIP was not completed. The API changes were merged (which was probably a mistake) before the implementation was ready to go. @BewareMyPower was just asking about this, too: #12104 (comment). I am not exactly sure how to move forward, but I will think about it a bit this week and try to figure something out.

michaeljmarshall added a commit that referenced this issue Jan 19, 2023
…19197)

PIP: #12105 

### Motivation

This is the first of several PRs to implement [PIP 97](#12105).

This PR seeks to start to solve the fact that the `AuthenticationState` class currently authenticates `authData` twice instead of just once. This change is important to make before we are able to utilize the async methods introduced in #12104.

Historical context: #14044 introduced the `AuthenticationProvider#newHttpAuthState`  method. The only use case for this method in the pulsar code base is to let custom providers specify the `AuthenticationDataSource` on http request attributes. The primary problem with that implementation is that the `AuthenticationState` class currently involves authenticating the `authData` passed in to the `newHttpAuthState`. As such, this code is sub-optimal, and creates a confusing flow.

I propose we deprecate the `newHttpAuthState` method and instead start using the `authenticateHttpRequestAsync` and `authenticateHttpRequest` methods to allow custom implementations to configure the desired `AuthenticationDataSource` on the request attributes.

In order to simplify the diff for reviewers, this PR uses the deprecated `AuthenticationProvider#authenticateHttpRequest` method. I plan to follow up and switch to use the `AuthenticationProvider#authenticateHttpRequestAsync` method.

Note that these changes are completely backwards compatible. The only risk is to users that have custom code loaded into the broker that calls the `AuthenticationProvider#authenticateHttpRequest` method.

### Modifications

* Deprecate `AuthenticationService#authenticateHttpRequest(HttpServletRequest request, AuthenticationDataSource authData)`. It is no longer used.
* Deprecate `AuthenticationProvider#newHttpAuthState(HttpServletRequest request)`. It is no longer used outside of the `AuthenticationProvider` interface itself.
* Remove `@InterfaceStability.Unstable` annotation from the `authenticateHttpRequestAsync` method. When I introduced that annotation, I was under the impression that we didn't need it. However, in order to meet the requirements introduced in #14044, we need to let custom `AuthenticationProviders` add their own attributes.
* Update the default implementation of `authenticateHttpRequest`. Because the previous implementation was unreachable by all auth providers except for the SASL implementation, this is a backwards compatible change.
* Implement changes for the `AuthenticationProviderToken` so that it no longer relies on `newHttpAuthState`.

### Verifying this change

I added new tests.

### Does this pull request potentially affect one of the following parts:

- [x] The public API

This changes the public API within the broker by marking some methods as `@Deprecated`.

### Documentation

- [x] `doc-not-needed`

We document the `AuthenticationProvider` interface in the code. I added these docs. There is currently no where else to update docs.

### Matching PR in forked repository

PR in forked repository: michaeljmarshall#12

### Additional motivation from PR discussion

My primary motivation for this PR is to implement PIP 97. If we want authentication to be asynchronous, we cannot assume that when the `AuthenticationState` object is initialized, the `authenticationDataSource` and the `authRole` are present because the authentication might not yet be completed. My goal with this PR is to break an unnecessary relationship between `AuthenticationState` and http request authentication that was created by #14044.
michaeljmarshall added a commit that referenced this issue Jan 25, 2023
PIP: #12105 

### Motivation

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here #19311.

### Modifications

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

### Verifying this change

A new test is added. The added test covers the change made in #19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

### Does this pull request potentially affect one of the following parts:

This is not a breaking change.

### Documentation

- [x] `doc-not-needed`

### Matching PR in forked repository

PR in forked repository: michaeljmarshall#18
@michaeljmarshall
Copy link
Member Author

Quick update: I've been working on this and am getting close to completing the Netty handler implementations. The HTTP filter and the websocket implementations are a bit trickier and will need more attention.

michaeljmarshall added a commit that referenced this issue Feb 1, 2023
PIP: #12105 

### Motivation

Implement asynchronous auth for the proxy connection. This is one of the core PRs for implementing #12105. 

### Modifications

* Update `ProxyConnection` class to asynchronously handle the authentication result. The result is handled on the handler's event loop to ensure correctness.
* Update `ProxyAuthenticationTest` class to implement async auth methods and to make authentication asynchronous to test that code path.

### Verifying this change

There is an updated test, but it doesn't cover all code paths in this PR.

### Documentation

- [x] `doc-not-needed`

We do not need to document this portion of PIP 97.

### Matching PR in forked repository

PR in forked repository: michaeljmarshall#16
@michaeljmarshall
Copy link
Member Author

As far as I can tell, the only remaining tasks for this PIP are the AbstractWebSocketHandler and the AuthenticationFilter.

One problem with the websocket proxy is that it does not have the request object available in the AuthenticationFilter.

authRole = service.getAuthenticationService()
.authenticateHttpRequest(request, authenticationState.getAuthDataSource());

I started work on the async http filter, but I ran into issues. Here is the start of that work: michaeljmarshall@605419f.

michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Feb 15, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Feb 15, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
(cherry picked from commit 3ef3bf1)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Feb 16, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
(cherry picked from commit 3ef3bf1)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Feb 17, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
(cherry picked from commit 3ef3bf1)
michaeljmarshall added a commit to datastax/pulsar that referenced this issue Feb 17, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: michaeljmarshall#18

(cherry picked from commit 8049690)
(cherry picked from commit 3ef3bf1)
(cherry picked from commit 168aa6a)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Feb 17, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
(cherry picked from commit 3ef3bf1)
(cherry picked from commit 467cd32)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Feb 22, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
(cherry picked from commit 3ef3bf1)
(cherry picked from commit 467cd32)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Feb 23, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
(cherry picked from commit 3ef3bf1)
(cherry picked from commit 467cd32)
(cherry picked from commit 1fab71b)
RobertIndie pushed a commit that referenced this issue Apr 19, 2023
…ist (#20132)

PIP: #12105 and #19771 

### Motivation

With the implementation of asynchronous authentication in PIP 97, I missed a case in the `AuthenticationProviderList` where we need to implement the `authenticateAsync` methods. This PR is necessary for making the `AuthenticationProviderToken` and the `AuthenticationProviderOpenID` work together, which is necessary for anyone transitioning to `AuthenticationProviderOpenID`.

### Modifications

* Implement `AuthenticationListState#authenticateAsync` using a recursive algorithm that first attempts to authenticate the client using the current `authState` and then tries the remaining options.
* Implement `AuthenticationProviderList#authenticateAsync` using a recursive algorithm that attempts each provider sequentially.
* Add test to `AuthenticationProviderListTest` that exercises this method. It didn't technically fail previously, but it's worth adding.
* Add test to `AuthenticationProviderOpenIDIntegrationTest` to cover the exact failures that were causing problems.
RobertIndie pushed a commit that referenced this issue Apr 19, 2023
…ist (#20132)

PIP: #12105 and #19771

### Motivation

With the implementation of asynchronous authentication in PIP 97, I missed a case in the `AuthenticationProviderList` where we need to implement the `authenticateAsync` methods. This PR is necessary for making the `AuthenticationProviderToken` and the `AuthenticationProviderOpenID` work together, which is necessary for anyone transitioning to `AuthenticationProviderOpenID`.

### Modifications

* Implement `AuthenticationListState#authenticateAsync` using a recursive algorithm that first attempts to authenticate the client using the current `authState` and then tries the remaining options.
* Implement `AuthenticationProviderList#authenticateAsync` using a recursive algorithm that attempts each provider sequentially.
* Add test to `AuthenticationProviderListTest` that exercises this method. It didn't technically fail previously, but it's worth adding.
* Add test to `AuthenticationProviderOpenIDIntegrationTest` to cover the exact failures that were causing problems.

(cherry picked from commit 58ccf02)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Apr 19, 2023
…tion 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)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Apr 19, 2023
…pache#19197)

PIP: apache#12105

This is the first of several PRs to implement [PIP 97](apache#12105).

This PR seeks to start to solve the fact that the `AuthenticationState` class currently authenticates `authData` twice instead of just once. This change is important to make before we are able to utilize the async methods introduced in apache#12104.

Historical context: apache#14044 introduced the `AuthenticationProvider#newHttpAuthState`  method. The only use case for this method in the pulsar code base is to let custom providers specify the `AuthenticationDataSource` on http request attributes. The primary problem with that implementation is that the `AuthenticationState` class currently involves authenticating the `authData` passed in to the `newHttpAuthState`. As such, this code is sub-optimal, and creates a confusing flow.

I propose we deprecate the `newHttpAuthState` method and instead start using the `authenticateHttpRequestAsync` and `authenticateHttpRequest` methods to allow custom implementations to configure the desired `AuthenticationDataSource` on the request attributes.

In order to simplify the diff for reviewers, this PR uses the deprecated `AuthenticationProvider#authenticateHttpRequest` method. I plan to follow up and switch to use the `AuthenticationProvider#authenticateHttpRequestAsync` method.

Note that these changes are completely backwards compatible. The only risk is to users that have custom code loaded into the broker that calls the `AuthenticationProvider#authenticateHttpRequest` method.

* Deprecate `AuthenticationService#authenticateHttpRequest(HttpServletRequest request, AuthenticationDataSource authData)`. It is no longer used.
* Deprecate `AuthenticationProvider#newHttpAuthState(HttpServletRequest request)`. It is no longer used outside of the `AuthenticationProvider` interface itself.
* Remove `@InterfaceStability.Unstable` annotation from the `authenticateHttpRequestAsync` method. When I introduced that annotation, I was under the impression that we didn't need it. However, in order to meet the requirements introduced in apache#14044, we need to let custom `AuthenticationProviders` add their own attributes.
* Update the default implementation of `authenticateHttpRequest`. Because the previous implementation was unreachable by all auth providers except for the SASL implementation, this is a backwards compatible change.
* Implement changes for the `AuthenticationProviderToken` so that it no longer relies on `newHttpAuthState`.

I added new tests.

- [x] The public API

This changes the public API within the broker by marking some methods as `@Deprecated`.

- [x] `doc-not-needed`

We document the `AuthenticationProvider` interface in the code. I added these docs. There is currently no where else to update docs.

PR in forked repository: #12

My primary motivation for this PR is to implement PIP 97. If we want authentication to be asynchronous, we cannot assume that when the `AuthenticationState` object is initialized, the `authenticationDataSource` and the `authRole` are present because the authentication might not yet be completed. My goal with this PR is to break an unnecessary relationship between `AuthenticationState` and http request authentication that was created by apache#14044.

(cherry picked from commit 3c38ed5)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Apr 19, 2023
…he#19312)

PIP: apache#12105

When authentication fails in the `ServerCnx`, the state is left in `Start` if the primary `authData` fails authentication and in `Connecting` or `Connected` if the `originalAuthData` authentication fails. To prevent any kind of unexpected behavior, we should go to `Failed` state.

Note that the tests verify the current behavior where a failed `originalAuthData` results first in a `Connected` command from the broker and then an `Error` command. I documented that I think this is sub optimal here apache#19311.

* Update `ServerCnx` state to `Failed` when there is an authentication exception during `handleConnect` and during `handleAuthResponse`.
* Update `handleAuthResponse` reply to `"Unable to authenticate"` instead of the `AuthenticationState` exception.

A new test is added. The added test covers the change made in apache#19295 where we updated `ServerCnx` so that we call `AuthState#authenticate` instead of relying on the implementation detail that the initialization calls `authenticate`. That PR should have added a test.

This is not a breaking change.

- [x] `doc-not-needed`

PR in forked repository: #18

(cherry picked from commit 8049690)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Apr 19, 2023
PIP: apache#12105

Implement asynchronous auth for the proxy connection. This is one of the core PRs for implementing apache#12105.

* Update `ProxyConnection` class to asynchronously handle the authentication result. The result is handled on the handler's event loop to ensure correctness.
* Update `ProxyAuthenticationTest` class to implement async auth methods and to make authentication asynchronous to test that code path.

There is an updated test, but it doesn't cover all code paths in this PR.

- [x] `doc-not-needed`

We do not need to document this portion of PIP 97.

PR in forked repository: #16

(cherry picked from commit fa6af43)
michaeljmarshall added a commit to michaeljmarshall/pulsar that referenced this issue Apr 20, 2023
…ist (apache#20132)

PIP: apache#12105 and apache#19771

With the implementation of asynchronous authentication in PIP 97, I missed a case in the `AuthenticationProviderList` where we need to implement the `authenticateAsync` methods. This PR is necessary for making the `AuthenticationProviderToken` and the `AuthenticationProviderOpenID` work together, which is necessary for anyone transitioning to `AuthenticationProviderOpenID`.

* Implement `AuthenticationListState#authenticateAsync` using a recursive algorithm that first attempts to authenticate the client using the current `authState` and then tries the remaining options.
* Implement `AuthenticationProviderList#authenticateAsync` using a recursive algorithm that attempts each provider sequentially.
* Add test to `AuthenticationProviderListTest` that exercises this method. It didn't technically fail previously, but it's worth adding.
* Add test to `AuthenticationProviderOpenIDIntegrationTest` to cover the exact failures that were causing problems.

(cherry picked from commit 58ccf02)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants