-
Notifications
You must be signed in to change notification settings - Fork 541
Existing OAuth users API OIDC Authentication #11671
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
Changes from 14 commits
4697002
12a079d
182fef3
b88fcc4
39b1de1
9f5c713
1168c01
948b149
bce5d50
fb0e559
12b62c4
2ce4dc5
5a43070
fe537ee
adf0e9f
5cca889
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 @@ | ||
| Implemented a new feature flag ``dataverse.feature.api-bearer-auth-use-oauth-user-on-id-match``, which supports the use of the new Dataverse client in instances that have historically allowed login via GitHub, ORCID, or Google. Specifically, with this flag enabled, when an OIDC bridge is configured to allow OIDC login with validation by the bridged OAuth providers, users with existing GitHub, ORCID, or Google accounts in Dataverse can log in to those accounts, thereby maintaining access to their existing content and retaining their roles. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3729,6 +3729,9 @@ please find all known feature flags below. Any of these flags can be activated u | |
| * - api-bearer-auth-use-shib-user-on-id-match | ||
| - Allows the use of a Shibboleth user account when an identity match is found during API bearer authentication. This feature enables automatic association of an incoming IdP identity with an existing Shibboleth user account, bypassing the need for additional user registration steps. This feature only works when the feature flag ``api-bearer-auth`` is also enabled. **Caution: Enabling this flag could result in impersonation risks if (and only if) used with a misconfigured IdP.** | ||
| - ``Off`` | ||
| * - api-bearer-auth-use-oauth-user-on-id-match | ||
| - Allows the use of an OAuth user account (GitHub, Google, or ORCID) when an identity match is found during API bearer authentication. This feature enables automatic association of an incoming IdP identity with an existing OAuth user account, bypassing the need for additional user registration steps. This feature only works when the feature flag ``api-bearer-auth`` is also enabled. **Caution: Enabling this flag could result in impersonation risks if (and only if) used with a misconfigured IdP.** | ||
|
Member
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'm confused. What is the impersonation risk? 🤔
Contributor
Author
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. If the OIDC claims include an identity provider ID and a user ID that match an authenticated user in Dataverse, but the provider that issued the claims is not actually the one that originally created that user, this would represent an impersonation issue. However, as mentioned in the docs, this scenario can only occur if the IdP is misconfigured. |
||
| - ``Off`` | ||
| * - avoid-expensive-solr-join | ||
| - Changes the way Solr queries are constructed for public content (published Collections, Datasets and Files). It removes a very expensive Solr join on all such documents, improving overall performance, especially for large instances under heavy load. Before this feature flag is enabled, the corresponding indexing feature (see next feature flag) must be turned on and a full reindex performed (otherwise public objects are not going to be shown in search results). See :doc:`/admin/solr-search-index`. | ||
| - ``Off`` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package edu.harvard.iq.dataverse.authorization; | ||
|
|
||
| import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GitHubOAuth2AP; | ||
|
|
||
| public class GitHubUserLookupParams extends OAuthUserLookupParams { | ||
|
|
||
| public GitHubUserLookupParams(String userId) { | ||
| super(userId); | ||
| } | ||
|
|
||
| @Override | ||
| public String getProviderId() { | ||
| return GitHubOAuth2AP.PROVIDER_ID; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package edu.harvard.iq.dataverse.authorization; | ||
|
|
||
| import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GoogleOAuth2AP; | ||
|
|
||
| public class GoogleUserLookupParams extends OAuthUserLookupParams { | ||
|
|
||
| public GoogleUserLookupParams(String userId) { | ||
| super(userId); | ||
| } | ||
|
|
||
| @Override | ||
| public String getProviderId() { | ||
| return GoogleOAuth2AP.PROVIDER_ID; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package edu.harvard.iq.dataverse.authorization; | ||
|
|
||
| abstract class OAuthUserLookupParams { | ||
|
|
||
| protected String userId; | ||
|
|
||
| public OAuthUserLookupParams(String userId) { | ||
| this.userId = userId; | ||
| } | ||
|
|
||
| public String getLookupUserId() { | ||
| return userId; | ||
| } | ||
|
|
||
| public abstract String getProviderId(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package edu.harvard.iq.dataverse.authorization; | ||
|
|
||
| import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GitHubOAuth2AP; | ||
| import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GoogleOAuth2AP; | ||
| import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.OrcidOAuth2AP; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
|
|
||
| /** | ||
| * A factory for creating {@link OAuthUserLookupParams} instances based on an identity provider. | ||
| * This is a non-instantiable utility class. | ||
| */ | ||
| public final class OAuthUserLookupParamsFactory { | ||
|
|
||
| /** | ||
| * A map linking provider IDs to their corresponding user searcher constructor. | ||
| */ | ||
| private static final Map<String, Function<String, OAuthUserLookupParams>> PROVIDER_MAP = Map.of( | ||
| GoogleOAuth2AP.PROVIDER_ID, GoogleUserLookupParams::new, | ||
| GitHubOAuth2AP.PROVIDER_ID, GitHubUserLookupParams::new, | ||
| OrcidOAuth2AP.PROVIDER_ID, ORCIDUserLookupParams::new | ||
| ); | ||
|
|
||
| private OAuthUserLookupParamsFactory() { | ||
| // Prevent instantiation of this utility class. | ||
| } | ||
|
|
||
| /** | ||
| * Creates an instance of an {@link OAuthUserLookupParams} based on the identity provider claim. | ||
| * | ||
| * @param idpClaim The identity provider claim value (e.g., "https://accounts.google.com"). | ||
| * @param userId The user identifier from the OAuth provider. | ||
| * @return A new instance of a concrete {@link OAuthUserLookupParams}. | ||
| * @throws IllegalArgumentException if the identity provider is not supported. | ||
| */ | ||
| public static OAuthUserLookupParams getOAuthUserLookupParams(String idpClaim, String userId) { | ||
| return PROVIDER_MAP.keySet().stream() | ||
| .filter(idpClaim::contains) | ||
| .findFirst() | ||
| .map(providerId -> PROVIDER_MAP.get(providerId).apply(userId)) | ||
| .orElseThrow(() -> new IllegalArgumentException("Unsupported OAuth provider: " + idpClaim)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package edu.harvard.iq.dataverse.authorization; | ||
|
|
||
| import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.OrcidOAuth2AP; | ||
|
|
||
| public class ORCIDUserLookupParams extends OAuthUserLookupParams { | ||
|
|
||
| private static final String ORCID_BASE_URL = "http://orcid.org/"; | ||
| private static final String ORCID_BASE_URL_HTTPS = "https://orcid.org/"; | ||
|
|
||
| public ORCIDUserLookupParams(String userId) { | ||
| super(userId); | ||
| } | ||
|
|
||
| @Override | ||
| public String getLookupUserId() { | ||
| return extractIdFromUrl(userId); | ||
| } | ||
|
|
||
| @Override | ||
| public String getProviderId() { | ||
| return OrcidOAuth2AP.PROVIDER_ID; | ||
| } | ||
|
|
||
| /** | ||
| * Extracts the ORCID iD from a full ORCID URL. | ||
| * <p> | ||
| * This method checks if the provided string starts with "http://orcid.org/" or "https://orcid.org/" | ||
| * and, if so, returns the trailing part of the string. If the string does not | ||
| * match the base URL, it is returned as-is, assuming it might already be the ID. | ||
| * | ||
| * @param orcidUrlOrId The full ORCID URL (e.g., "http://orcid.org/0009-0007-1267-8782") | ||
| * or an ORCID iD itself. | ||
| * @return The extracted ORCID iD (e.g., "0009-0007-1267-8782"), or the original string if it's not a URL. | ||
| * Returns null if the input is null. | ||
| */ | ||
| private static String extractIdFromUrl(String orcidUrlOrId) { | ||
| if (orcidUrlOrId == null) { | ||
| return null; | ||
| } | ||
| if (orcidUrlOrId.startsWith(ORCID_BASE_URL)) { | ||
| return orcidUrlOrId.substring(ORCID_BASE_URL.length()); | ||
| } | ||
| if (orcidUrlOrId.startsWith(ORCID_BASE_URL_HTTPS)) { | ||
| return orcidUrlOrId.substring(ORCID_BASE_URL_HTTPS.length()); | ||
| } | ||
| // If it's not a URL, assume it's already the ID. | ||
| return orcidUrlOrId; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.