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

Authentication fixes #8006

Merged
merged 2 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.quarkus.arc.processor.BeanConfigurator;
import io.quarkus.arc.processor.BeanConfiguratorBase;
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.runtime.RuntimeValue;

/**
Expand Down Expand Up @@ -36,16 +37,22 @@ public ExtendedBeanConfigurator configurator() {
return configurator;
}

public boolean isStaticInit() {
return configurator.staticInit;
}

/**
* This construct is not thread-safe and should not be reused.
*/
public static class ExtendedBeanConfigurator extends BeanConfiguratorBase<ExtendedBeanConfigurator, Object> {

Supplier<?> supplier;
RuntimeValue<?> runtimeValue;
boolean staticInit;

ExtendedBeanConfigurator(DotName implClazz) {
super(implClazz);
this.staticInit = true;
}

/**
Expand Down Expand Up @@ -73,6 +80,18 @@ public ExtendedBeanConfigurator runtimeValue(RuntimeValue<?> runtimeValue) {
return this;
}

/**
* By default, synthetic beans are initialized during {@link ExecutionTime#STATIC_INIT}. It is possible to mark a
* synthetic bean to be initialized during {@link ExecutionTime#RUNTIME_INIT}. However, in such case a client that
* attempts to obtain such bean during {@link ExecutionTime#STATIC_INIT} will receive an exception.
*
* @return self
*/
public ExtendedBeanConfigurator setRuntimeInit() {
this.staticInit = false;
return this;
}

public DotName getImplClazz() {
return implClazz;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.util.function.Consumer;
import java.util.function.Supplier;

import javax.enterprise.inject.CreationException;

import org.jboss.jandex.DotName;

import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem;
Expand Down Expand Up @@ -71,21 +73,42 @@ void build(ArcRecorder recorder, List<RuntimeBeanBuildItem> runtimeBeans, List<S
}

for (SyntheticBeanBuildItem bean : syntheticBeans) {
DotName implClazz = bean.configurator().getImplClazz();
String name = createName(implClazz.toString(), bean.configurator().getQualifiers().toString());
if (bean.configurator().runtimeValue != null) {
suppliersMap.put(name, recorder.createSupplier(bean.configurator().runtimeValue));
} else {
suppliersMap.put(name, bean.configurator().supplier);
if (bean.isStaticInit()) {
initSyntheticBean(recorder, suppliersMap, beanRegistration, bean);
}
beanRegistration.getContext().configure(implClazz)
.read(bean.configurator())
.creator(creator(name))
.done();
}

// Init the map of bean instances
recorder.initSupplierBeans(suppliersMap);
recorder.initStaticSupplierBeans(suppliersMap);
}

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void build(ArcRecorder recorder, List<SyntheticBeanBuildItem> syntheticBeans,
BeanRegistrationPhaseBuildItem beanRegistration, BuildProducer<BeanConfiguratorBuildItem> configurators) {

Map<String, Supplier<?>> suppliersMap = new HashMap<>();

for (SyntheticBeanBuildItem bean : syntheticBeans) {
if (!bean.isStaticInit()) {
initSyntheticBean(recorder, suppliersMap, beanRegistration, bean);
}
}
recorder.initRuntimeSupplierBeans(suppliersMap);
}

private void initSyntheticBean(ArcRecorder recorder, Map<String, Supplier<?>> suppliersMap,
BeanRegistrationPhaseBuildItem beanRegistration, SyntheticBeanBuildItem bean) {
DotName implClazz = bean.configurator().getImplClazz();
String name = createName(implClazz.toString(), bean.configurator().getQualifiers().toString());
if (bean.configurator().runtimeValue != null) {
suppliersMap.put(name, recorder.createSupplier(bean.configurator().runtimeValue));
} else {
suppliersMap.put(name, bean.configurator().supplier);
}
beanRegistration.getContext().configure(implClazz)
.read(bean.configurator())
.creator(creator(name))
.done();
}

private String createName(String beanClass, String qualifiers) {
Expand All @@ -102,6 +125,9 @@ public void accept(MethodCreator m) {
ResultHandle supplier = m.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class), staticMap,
m.load(name));
// Throw an exception if no supplier is found
m.ifNull(supplier).trueBranch().throwException(CreationException.class,
"Synthetic bean instance not initialized yet: " + name);
ResultHandle result = m.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Supplier.class, "get", Object.class),
supplier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,14 @@ public void initExecutor(ExecutorService executor) {
Arc.setExecutor(executor);
}

public void initSupplierBeans(Map<String, Supplier<?>> beans) {
public void initStaticSupplierBeans(Map<String, Supplier<?>> beans) {
supplierMap = new ConcurrentHashMap<>(beans);
}

public void initRuntimeSupplierBeans(Map<String, Supplier<?>> beans) {
supplierMap.putAll(beans);
}

public BeanContainer initBeanContainer(ArcContainer container, List<BeanContainerListener> listeners,
Collection<String> removedBeanTypes)
throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.elytron.security.oauth2.runtime.auth;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

Expand All @@ -10,9 +12,11 @@
import io.quarkus.security.credential.TokenCredential;
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.vertx.ext.web.RoutingContext;

/**
Expand Down Expand Up @@ -53,4 +57,14 @@ public CompletionStage<ChallengeData> getChallenge(RoutingContext context) {
"Bearer {token}");
return CompletableFuture.completedFuture(result);
}

@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return Collections.singleton(TokenAuthenticationRequest.class);
}

@Override
public HttpCredentialTransport getCredentialTransport() {
return new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, "bearer");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

Expand All @@ -19,10 +21,11 @@
import io.quarkus.security.credential.PasswordCredential;
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.UsernamePasswordAuthenticationRequest;
import io.quarkus.vertx.http.runtime.security.ChallengeData;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import io.undertow.security.idm.IdentityManager;
import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport;
import io.vertx.ext.web.RoutingContext;

/**
Expand All @@ -36,8 +39,6 @@ public class CustomAuth implements HttpAuthenticationMechanism {
private static final int PREFIX_LENGTH = BASIC_PREFIX.length();
private static final String COLON = ":";

private IdentityManager identityManager;

@Override
public CompletionStage<SecurityIdentity> authenticate(RoutingContext context,
IdentityProviderManager identityProviderManager) {
Expand Down Expand Up @@ -83,4 +84,14 @@ public CompletionStage<ChallengeData> getChallenge(RoutingContext context) {
"BASIC realm=CUSTOM");
return CompletableFuture.completedFuture(result);
}

@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return Collections.singleton(UsernamePasswordAuthenticationRequest.class);
}

@Override
public HttpCredentialTransport getCredentialTransport() {
return new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, "basic");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.oidc.runtime;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CompletionStage;

import javax.enterprise.context.ApplicationScoped;
Expand All @@ -8,8 +10,11 @@
import io.quarkus.oidc.OIDCException;
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.vertx.ext.web.RoutingContext;

@ApplicationScoped
Expand Down Expand Up @@ -41,4 +46,15 @@ private boolean isWebApp(RoutingContext context) {
return OidcTenantConfig.ApplicationType.WEB_APP == tenantContext.oidcConfig.applicationType;
}

@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return Collections.singleton(TokenAuthenticationRequest.class);
}

@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, "bearer");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fine, we can support bearer,code-flow in the future when a given mechanism supports different types

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.vertx.core.http.HttpHeaders.COOKIE;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
Expand All @@ -18,9 +19,11 @@
import io.quarkus.security.credential.TokenCredential;
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.smallrye.jwt.auth.AbstractBearerTokenExtractor;
import io.smallrye.jwt.auth.cdi.PrincipalProducer;
import io.smallrye.jwt.auth.principal.JWTAuthContextInfo;
Expand All @@ -32,6 +35,9 @@
*/
@ApplicationScoped
public class JWTAuthMechanism implements HttpAuthenticationMechanism {
protected static final String COOKIE_HEADER = "Cookie";
protected static final String AUTHORIZATION_HEADER = "Authorization";
protected static final String BEARER = "Bearer";

@Inject
private JWTAuthContextInfo authContextInfo;
Expand Down Expand Up @@ -90,4 +96,26 @@ protected String getCookieValue(String cookieName) {
return cookie != null ? cookie.getValue() : null;
}
}

@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return Collections.singleton(TokenAuthenticationRequest.class);
}

@Override
public HttpCredentialTransport getCredentialTransport() {
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);
} else if (AUTHORIZATION_HEADER.equals(tokenHeaderName)) {
return new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, BEARER);
} else {
return new HttpCredentialTransport(HttpCredentialTransport.Type.OTHER_HEADER, tokenHeaderName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import java.util.Map;
import java.util.function.Supplier;

import javax.inject.Singleton;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand All @@ -20,6 +22,7 @@
import io.quarkus.vertx.http.runtime.security.BasicAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.DenySecurityPolicy;
import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticator;
import io.quarkus.vertx.http.runtime.security.HttpAuthorizer;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy;
Expand Down Expand Up @@ -50,14 +53,41 @@ public void builtins(BuildProducer<HttpSecurityPolicyBuildItem> producer, HttpBu

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void initFormAuth(
BeanContainerBuildItem beanContainerBuildItem,
SyntheticBeanBuildItem initFormAuth(
HttpSecurityRecorder recorder,
HttpBuildTimeConfig buildTimeConfig,
HttpConfiguration httpConfiguration) {
if (buildTimeConfig.auth.form.enabled) {
recorder.setupFormAuth(beanContainerBuildItem.getValue(), httpConfiguration, buildTimeConfig);
return SyntheticBeanBuildItem.configure(FormAuthenticationMechanism.class)
.types(HttpAuthenticationMechanism.class)
.setRuntimeInit()
.scope(Singleton.class)
.supplier(recorder.setupFormAuth(httpConfiguration, buildTimeConfig)).done();
}
return null;
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
SyntheticBeanBuildItem initBasicAuth(
HttpSecurityRecorder recorder,
HttpBuildTimeConfig buildTimeConfig) {
if (buildTimeConfig.auth.form.enabled && !buildTimeConfig.auth.basic) {
//if form auth is enabled and we are not then we don't install
return null;
}
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(BasicAuthenticationMechanism.class)
.types(HttpAuthenticationMechanism.class)
.setRuntimeInit()
.scope(Singleton.class)
.supplier(recorder.setupBasicAuth(buildTimeConfig));
if (!buildTimeConfig.auth.form.enabled && !buildTimeConfig.auth.basic) {
//if not explicitly enabled we make this a default bean, so it is the fallback if nothing else is defined
configurator.defaultBean();
}

return configurator.done();
}

@BuildStep
Expand All @@ -79,7 +109,6 @@ void setupAuthenticationMechanisms(
}

if (buildTimeConfig.auth.form.enabled) {
beanProducer.produce(AdditionalBeanBuildItem.unremovableOf(FormAuthenticationMechanism.class));
} else if (buildTimeConfig.auth.basic) {
beanProducer.produce(AdditionalBeanBuildItem.unremovableOf(BasicAuthenticationMechanism.class));
}
Expand Down
Loading