Skip to content

Commit

Permalink
Add support for known providers to OIDC
Browse files Browse the repository at this point in the history
This adds specifc config to enable OIDC login for the main OIDC
providers. Having explicit config options like this makes it easy to
search the documentation/dev UI for it.

Fixes quarkusio#20783
  • Loading branch information
stuartwdouglas committed Dec 14, 2021
1 parent ee7e6c9 commit aa51984
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkus.oidc.deployment;

import java.util.Optional;
import java.util.function.Supplier;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.oidc.OidcTenantConfig;

/**
* An interface that abstracts details of a well known OIDC provider (google, github etc)
*/
public final class KnownProviderSupplierBuildItem extends MultiBuildItem {

final String name;
final Supplier<Optional<OidcTenantConfig>> supplier;

public KnownProviderSupplierBuildItem(String name, Supplier<Optional<OidcTenantConfig>> supplier) {
this.name = name;
this.supplier = supplier;
}

public Supplier<Optional<OidcTenantConfig>> getSupplier() {
return supplier;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.quarkus.oidc.deployment;

import java.util.Collection;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;

import javax.inject.Singleton;

Expand Down Expand Up @@ -38,6 +40,7 @@
import io.quarkus.oidc.runtime.OidcRecorder;
import io.quarkus.oidc.runtime.OidcTokenCredentialProducer;
import io.quarkus.oidc.runtime.TenantConfigBean;
import io.quarkus.oidc.runtime.providers.KnownOIDCProvidersRecorder;
import io.quarkus.runtime.TlsConfig;
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
import io.quarkus.vertx.http.deployment.SecurityInformationBuildItem;
Expand Down Expand Up @@ -115,9 +118,13 @@ public SyntheticBeanBuildItem setup(
OidcConfig config,
OidcRecorder recorder,
CoreVertxBuildItem vertxBuildItem,
TlsConfig tlsConfig) {
TlsConfig tlsConfig,
List<KnownProviderSupplierBuildItem> knownProviders) {
return SyntheticBeanBuildItem.configure(TenantConfigBean.class).unremovable().types(TenantConfigBean.class)
.supplier(recorder.setup(config, vertxBuildItem.getVertx(), tlsConfig))
.supplier(recorder.setup(config, vertxBuildItem.getVertx(), tlsConfig,
knownProviders.stream()
.collect(Collectors.toMap(KnownProviderSupplierBuildItem::getName,
KnownProviderSupplierBuildItem::getSupplier))))
.destroyer(TenantConfigBean.Destroyer.class)
.scope(Singleton.class) // this should have been @ApplicationScoped but fails for some reason
.setRuntimeInit()
Expand Down Expand Up @@ -151,4 +158,28 @@ public boolean getAsBoolean() {
return config.enabled && config.defaultTokenCacheEnabled;
}
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
KnownProviderSupplierBuildItem github(KnownOIDCProvidersRecorder recorder) {
return new KnownProviderSupplierBuildItem("github", recorder.github());
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
KnownProviderSupplierBuildItem facebook(KnownOIDCProvidersRecorder recorder) {
return new KnownProviderSupplierBuildItem("facebook", recorder.facebook());
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
KnownProviderSupplierBuildItem google(KnownOIDCProvidersRecorder recorder) {
return new KnownProviderSupplierBuildItem("google", recorder.google());
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
KnownProviderSupplierBuildItem microsoft(KnownOIDCProvidersRecorder recorder) {
return new KnownProviderSupplierBuildItem("microsoft", recorder.microsoft());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Optional;

import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.runtime.providers.ProvidersConfig;
import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigGroup;
Expand Down Expand Up @@ -37,6 +38,12 @@ public class OidcConfig {
@ConfigItem
public TokenCache tokenCache = new TokenCache();

/**
* Config for well known OIDC providers
*/
@ConfigItem
public ProvidersConfig provider;

/**
* Default TokenIntrospection and UserInfo cache configuration.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,24 @@ public Supplier<DefaultTokenIntrospectionUserInfoCache> setupTokenCache(OidcConf
return () -> new DefaultTokenIntrospectionUserInfoCache(config, vertx.get());
}

public Supplier<TenantConfigBean> setup(OidcConfig config, Supplier<Vertx> vertx, TlsConfig tlsConfig) {
public Supplier<TenantConfigBean> setup(OidcConfig config, Supplier<Vertx> vertx, TlsConfig tlsConfig,
Map<String, Supplier<Optional<OidcTenantConfig>>> knownProviders) {
final Vertx vertxValue = vertx.get();

String defaultTenantId = config.defaultTenant.getTenantId().orElse(DEFAULT_TENANT_ID);
TenantConfigContext defaultTenantContext = createStaticTenantContext(vertxValue, config.defaultTenant, tlsConfig,
defaultTenantId);

Map<String, TenantConfigContext> staticTenantsConfig = new HashMap<>();
for (Map.Entry<String, OidcTenantConfig> tenant : config.namedTenants.entrySet()) {
Map<String, OidcTenantConfig> tenants = new HashMap<>(config.namedTenants);
for (var e : knownProviders.entrySet()) {
var provider = e.getValue().get();
if (provider.isPresent()) {
tenants.put(e.getKey(), provider.get());
}
}

for (Map.Entry<String, OidcTenantConfig> tenant : tenants.entrySet()) {
OidcCommonUtils.verifyConfigurationId(defaultTenantId, tenant.getKey(), tenant.getValue().getTenantId());
staticTenantsConfig.put(tenant.getKey(),
createStaticTenantContext(vertxValue, tenant.getValue(), tlsConfig, tenant.getKey()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.quarkus.oidc.runtime.providers;

import java.util.List;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class Facebook {

/**
* The client ID
*/
@ConfigItem
public String clientId;

/**
* The secret
*/
@ConfigItem
public String secret;

/**
* List of scopes
*/
@ConfigItem(defaultValue = "email,public_profile")
public List<String> scopes;

/**
* Fields to retrieve from the user info endpoint
*/
@ConfigItem(defaultValue = "id,name,email,first_name,last_name")
public String userInfoFields;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.oidc.runtime.providers;

import java.util.List;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class GitHub {

/**
* The client ID
*/
@ConfigItem
public String clientId;

/**
* The secret
*/
@ConfigItem
public String secret;

/**
* List of scopes
*/
@ConfigItem(defaultValue = "user:email")
public List<String> scopes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.oidc.runtime.providers;

import java.util.List;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class Google {

/**
* The client ID
*/
@ConfigItem
public String clientId;

/**
* The secret
*/
@ConfigItem
public String secret;

/**
* List of scopes
*/
@ConfigItem(defaultValue = "openid,email,profile")
public List<String> scopes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.quarkus.oidc.runtime.providers;

import java.util.Optional;
import java.util.function.Supplier;

import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.runtime.OidcConfig;
import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class KnownOIDCProvidersRecorder {

final OidcConfig config;

public KnownOIDCProvidersRecorder(OidcConfig config) {
this.config = config;
}

public Supplier<Optional<OidcTenantConfig>> github() {
return new Supplier<Optional<OidcTenantConfig>>() {
@Override
public Optional<OidcTenantConfig> get() {
if (config.provider.github.isEmpty()) {
return Optional.empty();
}
OidcTenantConfig ret = new OidcTenantConfig();
ret.clientId = Optional.of(config.provider.github.get().clientId);
ret.credentials.secret = Optional.of(config.provider.github.get().secret);
ret.authServerUrl = Optional.of("https://github.com/login/oauth");
//TODO: do we want to hard code this?
ret.applicationType = OidcTenantConfig.ApplicationType.HYBRID;
ret.discoveryEnabled = false;
ret.authorizationPath = Optional.of("authorize");
ret.tokenPath = Optional.of("access_token");
ret.userInfoPath = Optional.of("https://api.github.com/user");
ret.authentication.scopes = Optional.of(config.provider.github.get().scopes);
ret.authentication.userInfoRequired = true;
ret.authentication.setIdTokenRequired(false);
ret.authentication.setRedirectPath("/Login/githubLoginSuccess");
return Optional.of(ret);
}
};
}

public Supplier<Optional<OidcTenantConfig>> google() {
return new Supplier<Optional<OidcTenantConfig>>() {
@Override
public Optional<OidcTenantConfig> get() {
if (config.provider.google.isEmpty()) {
return Optional.empty();
}
OidcTenantConfig ret = new OidcTenantConfig();
ret.clientId = Optional.of(config.provider.google.get().clientId);
ret.credentials.secret = Optional.of(config.provider.google.get().secret);
ret.authServerUrl = Optional.of("https://accounts.google.com");
//TODO: do we want to hard code this?
ret.applicationType = OidcTenantConfig.ApplicationType.HYBRID;
ret.authentication.scopes = Optional.of(config.provider.google.get().scopes);
ret.authentication.setRedirectPath("/Login/oidcLoginSuccess");
return Optional.of(ret);
}
};
}

public Supplier<Optional<OidcTenantConfig>> microsoft() {
return new Supplier<Optional<OidcTenantConfig>>() {
@Override
public Optional<OidcTenantConfig> get() {
if (config.provider.microsoft.isEmpty()) {
return Optional.empty();
}
OidcTenantConfig ret = new OidcTenantConfig();
ret.clientId = Optional.of(config.provider.microsoft.get().clientId);
ret.credentials.secret = Optional.of(config.provider.microsoft.get().secret);
ret.authServerUrl = Optional.of("https://login.microsoftonline.com/common/v2.0");
//TODO: do we want to hard code this?
ret.applicationType = OidcTenantConfig.ApplicationType.HYBRID;
ret.authentication.setRedirectPath("/Login/oidcLoginSuccess");
ret.token.setIssuer("any");
return Optional.of(ret);
}
};
}

public Supplier<Optional<OidcTenantConfig>> facebook() {
return new Supplier<Optional<OidcTenantConfig>>() {
@Override
public Optional<OidcTenantConfig> get() {
if (config.provider.facebook.isEmpty()) {
return Optional.empty();
}
OidcTenantConfig ret = new OidcTenantConfig();
ret.clientId = Optional.of(config.provider.facebook.get().clientId);
ret.credentials.secret = Optional.of(config.provider.facebook.get().secret);
ret.authServerUrl = Optional.of("https://www.facebook.com");
ret.authentication.scopes = Optional.of(config.provider.facebook.get().scopes);
ret.applicationType = OidcTenantConfig.ApplicationType.HYBRID;
ret.authentication.setRedirectPath("/Login/facebookLoginSuccess");
ret.discoveryEnabled = false;
ret.tokenPath = Optional.of("https://graph.facebook.com/v12.0/oauth/access_token");
ret.token.setIssuer("facebook");
ret.setAuthorizationPath("https://facebook.com/dialog/oauth/");
ret.setJwksPath("https://www.facebook.com/.well-known/oauth/openid/jwks/");
ret.setUserInfoPath("https://graph.facebook.com/me/?fields=" + config.provider.facebook.get().userInfoFields);
ret.authentication.setUserInfoRequired(true);
ret.authentication.setIdTokenRequired(false);
return Optional.of(ret);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.oidc.runtime.providers;

import java.util.List;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class Microsoft {

/**
* The client ID
*/
@ConfigItem
public String clientId;

/**
* The secret
*/
@ConfigItem
public String secret;
/**
* List of scopes
*/
@ConfigItem(defaultValue = "openid,email,profile")
public List<String> scopes;
}
Loading

0 comments on commit aa51984

Please sign in to comment.