-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Add support for known providers to OIDC #22176
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
|
@@ -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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, but it is not correct - it creates a dedicated tenant per every social provider config, but it won't allow to override a specific property using But also equally important, it does not allow to vary with the provider, each OIDC tenant allows to have:
It may not be something one would want to do in production but for tests it is very useful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, so continuing from the earlier comment, so you have
|
||
} | ||
} | ||
|
||
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())); | ||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I now think that these groups have to be removed, IMHO, the supplied |
||
|
||
/** | ||
* 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think those links are too specific to my app. But in practice:
Perhaps a better endpoint default would be: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naturally, a further layer of abstraction which knows how to fetch the damn fields from all providers and present them to the Quarkus user would be even better: then I only need a single success endpoint :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it has to be an application specific property |
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, not sure, I'd have to test it on my app to see if it works. |
||
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it has to be decoupled from
OidcTenantConfig
, it has to be part ofOidcTenantConfig
, tenant specific as needed, or rather, IMHO, something likequarkus.oidc.provider=github
should point to an instance ofOidcTenantConfig
, merged with theOidcTenantConfig
referencing it, please see belowThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, just realized, this way won't work with the dynamic
TenantConfigResolver
- one should be able to do, instead of defining this config statically inapplication.properties
,There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stuartwdouglas Stuart, how about having an
OidcTenantConfig
provider
enum property with (github
, etc, values).This will allow
OidcRecorder
, when going over everyOidcTenantConfig
, pick up a referenced well-known providerOidcTenantConfig
instance and merge with the currentOidcTenantConfig
- it will address all the concerns I've expressed in various comments here