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

Rethink order of JAX-RS filters in Polaris #819

Open
adutra opened this issue Jan 17, 2025 · 2 comments · May be fixed by #848
Open

Rethink order of JAX-RS filters in Polaris #819

adutra opened this issue Jan 17, 2025 · 2 comments · May be fixed by #848
Labels
enhancement New feature or request

Comments

@adutra
Copy link
Contributor

adutra commented Jan 17, 2025

Is your feature request related to a problem? Please describe.

Currently Polaris has a number of filters, some of them being "regular" JAX-RS filters, some others being Vert.x route filters:

Filter Type Priority
QuarkusLoggingMDCFilter Vert.x RouteFilter.DEFAULT_PRIORITY + 100 (110)
QuarkusTracingFilter Vert.x QuarkusLoggingMDCFilter.PRIORITY - 1 (109)
PolarisPrincipalAuthenticatorFilter JAX-RS Piorities.AUTHENTICATION (1000)
PolarisPrincipalRolesProviderFilter JAX-RS Piorities.AUTHENTICATION + 1 (1001)
RateLimiterFilter JAX-RS Piorities.USER (5000)

The above has a number of issues:

First off, from my experience, Vert.x filters always execute first, regardless of priorities (also, Vert.x priorities are inverted compared to JAX-RS ones). This induces a wrong order of execution:

  1. QuarkusLoggingMDCFilter
  2. QuarkusTracingFilter
  3. PolarisPrincipalAuthenticatorFilter
  4. PolarisPrincipalRolesProviderFilter
  5. RateLimiterFilter

Secondly, we ideally need a filter to verify realm IDs. For two reasons:

  1. The default realm ID resolver throws an exception if the realm is unknown. That's fine, but the error at that stage, since the resolver is a CDI bean, is translated into HTTP 500. If we want to return something more meaningful, like HTTP 403, we'd need to do that in a filter. Related to this: Throw NotAuthorizedException when realm is unknown #805.
  2. Also, QuarkusLoggingMDCFilter and QuarkusTracingFilter both need the realm ID. So they should execute after the realm ID filter.

Describe the solution you'd like

I think we need to reorder the filters as below:

  1. RealmIdFilter(to be created)
  2. QuarkusLoggingMDCFilter
  3. QuarkusTracingFilter
  4. PolarisPrincipalAuthenticatorFilter
  5. PolarisPrincipalRolesProviderFilter
  6. RateLimiterFilter

Or maybe as below if we want the authenticator to kick in first:

  1. PolarisPrincipalAuthenticatorFilter
  2. RealmIdFilter(to be created)
  3. PolarisPrincipalRolesProviderFilter
  4. QuarkusLoggingMDCFilter
  5. QuarkusTracingFilter
  6. RateLimiterFilter

We may need to convert the Vert.x filters into JAX-RS ones, or the other way around.

Describe alternatives you've considered

No response

Additional context

No response

@collado-mike
Copy link
Contributor

I guess I have missed the commit where RealmContextResolver has been deleted? That was the intention of that filter in the original code base.

The RealmIdFilter or RealmContextResolver must be executed prior to authentication - a caller can only be authenticated within a realm.

@adutra
Copy link
Contributor Author

adutra commented Jan 23, 2025

@collado-mike RealmContextResolver wasn't deleted, but renamed. Its default implementation is here now:

public class DefaultRealmIdResolver implements RealmIdResolver {

The RealmIdFilter or RealmContextResolver must be executed prior to authentication - a caller can only be authenticated within a realm.

Thanks for confirming, that was my conclusion as well.

For the record, it was already being resolved prior to authentication, but with one annoying difference: the realm was being resolved as part of CDI resolution, see here:

@Produces
@RequestScoped
public RealmId realmId(@Context HttpServerRequest request, RealmIdResolver realmIdResolver) {
return realmIdResolver.resolveRealmContext(
request.absoluteURI(),
request.method().name(),
request.path(),
request.headers().entries().stream()
.collect(HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), HashMap::putAll));
}

While this works well in the happy case (the realm can be resolved), it throws HTTP 500 in the unhappy case of wrong realm.

Hence the changes in #848 : realm resolution is moved to a pre-auth filter:

RealmId realmId = null;
try {
realmId = resolveRealmContext(rc);
} catch (NotAuthorizedException e) {
rc.abortWith(Response.status(Status.UNAUTHORIZED).build());
} catch (Exception e) {
rc.abortWith(Response.status(Status.INTERNAL_SERVER_ERROR).build());
}
rc.setProperty(REALM_ID_KEY, realmId);

The filter resolves the realm, then sets it as a request property. Then CDI now simply reads the realm ID from the request property and produces the request-scoped bean that will be injected everywhere:

@Produces
@RequestScoped
public RealmId realmId(@Context ContainerRequestContext request) {
return (RealmId) request.getProperty(RealmIdFilter.REALM_ID_KEY);
}

Does that make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants