Skip to content

Commit

Permalink
Resolve HttpCredentialTransport dynamically
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Jan 14, 2022
1 parent 4bd4e90 commit c977618
Show file tree
Hide file tree
Showing 17 changed files with 668 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.quarkus.oidc.OIDCException;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.AuthenticationRequest;
Expand All @@ -21,10 +22,13 @@

@ApplicationScoped
public class OidcAuthenticationMechanism implements HttpAuthenticationMechanism {
private static HttpCredentialTransport OIDC_SERVICE_TRANSPORT = new HttpCredentialTransport(
HttpCredentialTransport.Type.AUTHORIZATION, OidcConstants.BEARER_SCHEME);
private static HttpCredentialTransport OIDC_WEB_APP_TRANSPORT = new HttpCredentialTransport(
HttpCredentialTransport.Type.AUTHORIZATION_CODE, OidcConstants.CODE_FLOW_CODE);

private final BearerAuthenticationMechanism bearerAuth = new BearerAuthenticationMechanism();
private final CodeAuthenticationMechanism codeAuth = new CodeAuthenticationMechanism();

private final DefaultTenantConfigResolver resolver;

public OidcAuthenticationMechanism(DefaultTenantConfigResolver resolver) {
Expand All @@ -43,6 +47,7 @@ public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcConfig) {
if (!oidcConfig.tenantEnabled) {
return Uni.createFrom().nullItem();
}
context.put(IdentityProvider.class.getName(), OidcIdentityProvider.class.getName());
return isWebApp(context, oidcConfig) ? codeAuth.authenticate(context, identityProviderManager, oidcConfig)
: bearerAuth.authenticate(context, identityProviderManager, oidcConfig);
}
Expand Down Expand Up @@ -89,10 +94,18 @@ public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
}

@Override
public HttpCredentialTransport getCredentialTransport() {
//not 100% correct, but enough for now
//if OIDC is present we don't really want another bearer mechanism
return new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, OidcConstants.BEARER_SCHEME);
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
setTenantIdAttribute(context);
return resolve(context).onItem().transform(new Function<OidcTenantConfig, HttpCredentialTransport>() {
@Override
public HttpCredentialTransport apply(OidcTenantConfig oidcTenantConfig) {
if (!oidcTenantConfig.tenantEnabled) {
return null;
}
return isWebApp(context, oidcTenantConfig) ? OIDC_WEB_APP_TRANSPORT
: OIDC_SERVICE_TRANSPORT;
}
});
}

private static void setTenantIdAttribute(RoutingContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public Class<TokenAuthenticationRequest> getRequestType() {
public Uni<SecurityIdentity> authenticate(TokenAuthenticationRequest request,
AuthenticationRequestContext context) {
RoutingContext vertxContext = HttpSecurityUtils.getRoutingContextAttribute(request);
if (!this.getClass().getName().equals(vertxContext.get(IdentityProvider.class.getName()))) {
return Uni.createFrom().nullItem();
}
vertxContext.put(AuthenticationRequestContext.class.getName(), context);

Uni<TenantConfigContext> tenantConfigContext = tenantResolver.resolveContext(vertxContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.AuthenticationRequest;
import io.quarkus.security.identity.request.TokenAuthenticationRequest;
import io.quarkus.vertx.http.runtime.security.ChallengeData;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import io.smallrye.jwt.auth.AbstractBearerTokenExtractor;
import io.smallrye.jwt.auth.principal.JWTAuthContextInfo;
import io.smallrye.mutiny.Uni;
Expand All @@ -42,8 +44,10 @@ public Uni<SecurityIdentity> authenticate(RoutingContext context,
IdentityProviderManager identityProviderManager) {
String jwtToken = new VertxBearerTokenExtractor(authContextInfo, context).getBearerToken();
if (jwtToken != null) {
context.put(IdentityProvider.class.getName(), MpJwtValidator.class.getName());
return identityProviderManager
.authenticate(new TokenAuthenticationRequest(new JsonWebTokenCredential(jwtToken)));
.authenticate(HttpSecurityUtils.setRoutingContextAttribute(
new TokenAuthenticationRequest(new JsonWebTokenCredential(jwtToken)), context));
}
return Uni.createFrom().optional(Optional.empty());
}
Expand Down Expand Up @@ -93,19 +97,20 @@ public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
}

@Override
public HttpCredentialTransport getCredentialTransport() {
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
final String tokenHeaderName = authContextInfo.getTokenHeader();
if (COOKIE_HEADER.equals(tokenHeaderName)) {
String tokenCookieName = authContextInfo.getTokenCookie();

if (tokenCookieName == null) {
tokenCookieName = BEARER;
}
return new HttpCredentialTransport(HttpCredentialTransport.Type.COOKIE, tokenCookieName);
return Uni.createFrom().item(new HttpCredentialTransport(HttpCredentialTransport.Type.COOKIE, tokenCookieName));
} else if (AUTHORIZATION_HEADER.equals(tokenHeaderName)) {
return new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, BEARER);
return Uni.createFrom().item(new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, BEARER));
} else {
return new HttpCredentialTransport(HttpCredentialTransport.Type.OTHER_HEADER, tokenHeaderName);
return Uni.createFrom()
.item(new HttpCredentialTransport(HttpCredentialTransport.Type.OTHER_HEADER, tokenHeaderName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.TokenAuthenticationRequest;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import io.smallrye.jwt.auth.principal.JWTParser;
import io.smallrye.jwt.auth.principal.ParseException;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.subscription.UniEmitter;
import io.vertx.ext.web.RoutingContext;

/**
* Validates a bearer token according to the MP-JWT rules
Expand Down Expand Up @@ -49,6 +51,10 @@ public Class<TokenAuthenticationRequest> getRequestType() {
@Override
public Uni<SecurityIdentity> authenticate(TokenAuthenticationRequest request,
AuthenticationRequestContext context) {
RoutingContext vertxContext = HttpSecurityUtils.getRoutingContextAttribute(request);
if (!this.getClass().getName().equals(vertxContext.get(IdentityProvider.class.getName()))) {
return Uni.createFrom().nullItem();
}
if (!blockingAuthentication) {
return Uni.createFrom().emitter(new Consumer<UniEmitter<? super SecurityIdentity>>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
}

@Override
public HttpCredentialTransport getCredentialTransport() {
return new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, BASIC);
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return Uni.createFrom().item(new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, BASIC));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
}

@Override
public HttpCredentialTransport getCredentialTransport() {
return new HttpCredentialTransport(HttpCredentialTransport.Type.POST, postLocation, FORM);
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return Uni.createFrom().item(new HttpCredentialTransport(HttpCredentialTransport.Type.POST, postLocation, FORM));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,26 @@ default Uni<Boolean> sendChallenge(RoutingContext context) {
}

/**
* The credential transport, used to make sure multiple incompatible mechanisms are not installed
* The credential transport, used for finding the best candidate for authenticating and challenging when more than one
* mechanism is installed.
* and finding the best candidate for issuing a challenge when more than one mechanism is installed.
*
* May be null if this mechanism cannot interfere with other mechanisms
*/
HttpCredentialTransport getCredentialTransport();
@Deprecated
default HttpCredentialTransport getCredentialTransport() {
throw new UnsupportedOperationException();
}

/**
* The credential transport, used for finding the best candidate for authenticating and challenging when more than one
* mechanism is installed.
*
* May be null if this mechanism cannot interfere with other mechanisms
*/
default Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
throw new UnsupportedOperationException();
}

class ChallengeSender implements Function<ChallengeData, Boolean> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
Expand All @@ -29,6 +27,7 @@
*/
@Singleton
public class HttpAuthenticator {
private static final Uni<HttpAuthenticationMechanism> NULL_UNI = Uni.createFrom().nullItem();

private final IdentityProviderManager identityProviderManager;
private final Instance<PathMatchingHttpSecurityPolicy> pathMatchingPolicy;
Expand Down Expand Up @@ -71,21 +70,6 @@ public int compare(HttpAuthenticationMechanism mech1, HttpAuthenticationMechanis
}
});
this.mechanisms = mechanisms.toArray(new HttpAuthenticationMechanism[mechanisms.size()]);
//validate that we don't have multiple incompatible mechanisms
Map<HttpCredentialTransport, HttpAuthenticationMechanism> map = new HashMap<>();
for (HttpAuthenticationMechanism i : mechanisms) {
HttpCredentialTransport credentialTransport = i.getCredentialTransport();
if (credentialTransport == null) {
continue;
}
HttpAuthenticationMechanism existing = map.get(credentialTransport);
if (existing != null) {
throw new RuntimeException("Multiple mechanisms present that use the same credential transport "
+ credentialTransport + ". Mechanisms are " + i + " and " + existing);
}
map.put(credentialTransport, i);
}

}
}

Expand All @@ -108,14 +92,30 @@ public Uni<SecurityIdentity> attemptAuthentication(RoutingContext routingContext
String pathSpecificMechanism = pathMatchingPolicy.isResolvable()
? pathMatchingPolicy.get().getAuthMechanismName(routingContext)
: null;
HttpAuthenticationMechanism matchingMech = findBestCandidateMechanism(routingContext, pathSpecificMechanism);
if (matchingMech != null) {
routingContext.put(HttpAuthenticationMechanism.class.getName(), matchingMech);
return matchingMech.authenticate(routingContext, identityProviderManager);
} else if (pathSpecificMechanism != null) {
return Uni.createFrom().optional(Optional.empty());
Uni<HttpAuthenticationMechanism> matchingMechUni = findBestCandidateMechanism(routingContext, pathSpecificMechanism);
if (matchingMechUni == null) {
return createSecurityIdentity(routingContext);
}

return matchingMechUni.onItem()
.transformToUni(new Function<HttpAuthenticationMechanism, Uni<? extends SecurityIdentity>>() {

@Override
public Uni<SecurityIdentity> apply(HttpAuthenticationMechanism mech) {
if (mech != null) {
routingContext.put(HttpAuthenticationMechanism.class.getName(), mech);
return mech.authenticate(routingContext, identityProviderManager);
} else if (pathSpecificMechanism != null) {
return Uni.createFrom().optional(Optional.empty());
}
return createSecurityIdentity(routingContext);
}

});

}

private Uni<SecurityIdentity> createSecurityIdentity(RoutingContext routingContext) {
Uni<SecurityIdentity> result = mechanisms[0].authenticate(routingContext, identityProviderManager);
for (int i = 1; i < mechanisms.length; ++i) {
HttpAuthenticationMechanism mech = mechanisms[i];
Expand All @@ -129,7 +129,6 @@ public Uni<SecurityIdentity> apply(SecurityIdentity data) {
}
});
}

return result;
}

Expand Down Expand Up @@ -196,28 +195,83 @@ public Uni<? extends ChallengeData> apply(ChallengeData data) {
return result;
}

private HttpAuthenticationMechanism findBestCandidateMechanism(RoutingContext routingContext,
private Uni<HttpAuthenticationMechanism> findBestCandidateMechanism(RoutingContext routingContext,
String pathSpecificMechanism) {
Uni<HttpAuthenticationMechanism> result = null;

if (pathSpecificMechanism != null) {
for (int i = 0; i < mechanisms.length; ++i) {
HttpCredentialTransport credType = mechanisms[i].getCredentialTransport();
if (credType != null && credType.getAuthenticationScheme().equalsIgnoreCase(pathSpecificMechanism)) {
return mechanisms[i];
}
result = getPathSpecificMechanism(0, routingContext, pathSpecificMechanism);
for (int i = 1; i < mechanisms.length; ++i) {
int mechIndex = i;
result = result.onItem().transformToUni(
new Function<HttpAuthenticationMechanism, Uni<? extends HttpAuthenticationMechanism>>() {
@Override
public Uni<? extends HttpAuthenticationMechanism> apply(HttpAuthenticationMechanism mech) {
if (mech != null) {
return Uni.createFrom().item(mech);
}
return getPathSpecificMechanism(mechIndex, routingContext, pathSpecificMechanism);
}
});
}
} else {
String authScheme = getAuthorizationScheme(routingContext);
if (authScheme != null) {
for (int i = 0; i < mechanisms.length; ++i) {
HttpCredentialTransport credType = mechanisms[i].getCredentialTransport();
if (credType != null && credType.getTransportType() == Type.AUTHORIZATION
&& credType.getTypeTarget().toLowerCase().startsWith(authScheme.toLowerCase())) {
return mechanisms[i];
}
result = getAuthorizationSchemeMechanism(0, routingContext, authScheme);
for (int i = 1; i < mechanisms.length; ++i) {
int mechIndex = i;
result = result.onItem().transformToUni(
new Function<HttpAuthenticationMechanism, Uni<? extends HttpAuthenticationMechanism>>() {
@Override
public Uni<? extends HttpAuthenticationMechanism> apply(HttpAuthenticationMechanism mech) {
if (mech != null) {
return Uni.createFrom().item(mech);
}
return getAuthorizationSchemeMechanism(mechIndex, routingContext, authScheme);
}
});
}
}
}
return null;
return result;
}

private Uni<HttpAuthenticationMechanism> getPathSpecificMechanism(int index, RoutingContext routingContext,
String pathSpecificMechanism) {
return getCredentialTransport(mechanisms[index], routingContext).onItem()
.transform(new Function<HttpCredentialTransport, HttpAuthenticationMechanism>() {
@Override
public HttpAuthenticationMechanism apply(HttpCredentialTransport t) {
if (t != null && t.getAuthenticationScheme().equalsIgnoreCase(pathSpecificMechanism)) {
return mechanisms[index];
}
return null;
}
});
}

private Uni<HttpAuthenticationMechanism> getAuthorizationSchemeMechanism(int index, RoutingContext routingContext,
String authScheme) {
return getCredentialTransport(mechanisms[index], routingContext).onItem()
.transform(new Function<HttpCredentialTransport, HttpAuthenticationMechanism>() {
@Override
public HttpAuthenticationMechanism apply(HttpCredentialTransport t) {
if (t != null && t.getTransportType() == Type.AUTHORIZATION
&& t.getTypeTarget().toLowerCase().startsWith(authScheme.toLowerCase())) {
return mechanisms[index];
}
return null;
}
});
}

private static Uni<HttpCredentialTransport> getCredentialTransport(HttpAuthenticationMechanism mechanism,
RoutingContext routingContext) {
try {
return mechanism.getCredentialTransport(routingContext);
} catch (UnsupportedOperationException ex) {
return Uni.createFrom().item(mechanism.getCredentialTransport());
}
}

private static String getAuthorizationScheme(RoutingContext routingContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
* Authorization header
* POST
*
* It is not permitted for multiple HTTP authentication mechanisms to use the same credential
* transport type, as they will not be able to figure out which mechanisms should process which
* Not that using multiple HTTP authentication mechanisms to use the same credential
* transport type can lead to unexpected authentication failures as they will not be able to figure out which mechanisms should
* process which
* request.
*/
public class HttpCredentialTransport {
Expand Down Expand Up @@ -49,7 +50,11 @@ public enum Type {
/**
* X509
*/
X509
X509,
/**
* Authorizatiob code, type target is the query 'code' parameter
*/
AUTHORIZATION_CODE
}

@Override
Expand Down
Loading

0 comments on commit c977618

Please sign in to comment.