Skip to content

Add per-domain OAuth (Google, GitHub) provider support#12702

Draft
Damans227 wants to merge 34 commits intoapache:mainfrom
Damans227:oauth-per-domain
Draft

Add per-domain OAuth (Google, GitHub) provider support#12702
Damans227 wants to merge 34 commits intoapache:mainfrom
Damans227:oauth-per-domain

Conversation

@Damans227
Copy link
Contributor

Description

Add per-domain OAuth provider support. Allows OAuth providers (Google, GitHub) to be configured at the domain level with global fallback.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

How Has This Been Tested?

Manual testing with GitHub OAuth provider configured at domain level and global level, verifying domain-specific lookup with global fallback.

Daman Arora and others added 19 commits December 18, 2025 11:23
@Damans227
Copy link
Contributor Author

@blueorangutan package

@blueorangutan
Copy link

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✖️ el8 ✖️ el9 ✖️ debian ✖️ suse15. SL-JID 16931

@Damans227
Copy link
Contributor Author

@blueorangutan package

@blueorangutan
Copy link

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 16993

@Damans227
Copy link
Contributor Author

@blueorangutan package

@blueorangutan
Copy link

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✖️ el8 ✖️ el9 ✖️ debian ✖️ suse15. SL-JID 17012

@Damans227
Copy link
Contributor Author

@blueorangutan package

@blueorangutan
Copy link

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✖️ el8 ✖️ el9 ✖️ debian ✖️ suse15. SL-JID 17016

@Damans227
Copy link
Contributor Author

@blueorangutan package

@blueorangutan
Copy link

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17017

@blueorangutan
Copy link

[SF] Trillian Build Failed (tid-15566)

@Damans227
Copy link
Contributor Author

@blueorangutan package

@blueorangutan
Copy link

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17019

@blueorangutan
Copy link

[SF] Trillian Build Failed (tid-15568)

@blueorangutan
Copy link

[SF] Trillian Build Failed (tid-15569)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for configuring OAuth providers (Google/GitHub) per domain, with global fallback, and wires domain context through the UI and backend authentication flow.

Changes:

  • UI: Adds an “External” OAuth login tab with domain input and provider discovery.
  • Backend: Adds domain_id to OAuth providers, introduces domain-aware DAO lookups with global fallback, and resolves domain from API params for login/verification.
  • Tests/Schema: Updates unit tests for new method signatures and adds DB schema migration for per-domain providers.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
ui/src/views/dashboard/VerifyOauth.vue Sends domain context when verifying OAuth codes.
ui/src/views/auth/Login.vue Adds OAuth tab w/ domain-based provider discovery and domain-aware OAuth URLs.
ui/src/config/section/config.js Exposes domain fields and domainid arg in OAuth provider config UI.
plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java Updates mocks for domain-aware provider lookup.
plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java Updates stubbing for domain resolution and new verify method signature; adjusts authenticator setup test.
plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java Refactors test setup and populates domain-related response fields.
plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java Updates calls to domain-aware methods/signatures.
plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java Updates tests for domain-scoped enablement and list/register changes.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.java Adds domainId field to provider entity.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java Adds domain-aware verification and provider lookup w/ fallback.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java Adds domain-aware verification and access-token retrieval.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java Implements domain + global fallback queries and domain-inclusive listing.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java Adds domain-aware DAO APIs.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java Adds domain UUID/name fields in API response.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java Adds domain params and resolves domainId before verification.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java Returns domain info and checks enabled state in domain scope.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java Accepts domainId and returns domain info in response.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java Enforces OAuth enabled check in resolved domain scope.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java Adds domain filtering/visibility logic and domain-aware enabled evaluation.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java Uses domain-scoped enablement and domain-aware verification.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java Adds domain resolution, domain-aware list/register logic, and domain-aware verification call.
plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java Makes enablement domain-scoped and updates method signatures for domain awareness.
framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java Adds strictScope option to avoid fallback when desired.
engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql Adds domain_id to oauth_provider and uniqueness/indexing.
api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java Adds domain-aware methods for verification and code-to-email.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +42 to 47
public OauthProviderVO findByProviderAndDomain(String provider, Long domainId) {
SearchCriteria<OauthProviderVO> sc = oauthProviderSearchByProviderAndDomain.create();
sc.setParameters("provider", provider);

sc.setParameters("domainId", domainId);
return findOneBy(sc);
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

Using an EQ filter with a null domainId typically generates domain_id = NULL, which won’t match global rows (NULL) in SQL. This can break global provider lookup and allow duplicate global registrations. Consider handling domainId == null with an IS NULL criterion (or a separate SearchBuilder/query path) instead of setting an EQ parameter to null.

Copilot uses AI. Check for mistakes.
ALTER TABLE `cloud`.`oauth_provider` ADD CONSTRAINT `fk_oauth_provider__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`);
ALTER TABLE `cloud`.`oauth_provider` ADD INDEX `i_oauth_provider__domain_id`(`domain_id`);

ALTER TABLE `cloud`.`oauth_provider` ADD UNIQUE KEY `uk_oauth_provider__provider_domain` (`provider`, `domain_id`);
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

In MySQL, UNIQUE(provider, domain_id) does not prevent multiple rows where domain_id is NULL (NULLs are treated as distinct), so multiple 'global' providers with the same provider can still be inserted. If global uniqueness is required, consider a schema approach that normalizes NULL (e.g., a generated column like coalesce(domain_id, 0) with a unique index) or store a non-NULL sentinel for global and index that.

Suggested change
ALTER TABLE `cloud`.`oauth_provider` ADD UNIQUE KEY `uk_oauth_provider__provider_domain` (`provider`, `domain_id`);
ALTER TABLE `cloud`.`oauth_provider` ADD COLUMN `domain_id_norm` bigint unsigned AS (COALESCE(`domain_id`, 0)) STORED, ADD UNIQUE KEY `uk_oauth_provider__provider_domain` (`provider`, `domain_id_norm`);

Copilot uses AI. Check for mistakes.
protected boolean isOAuthPluginEnabled() {
return OAuth2IsPluginEnabled.value();
protected boolean isOAuthPluginEnabled(Long domainId) {
return Boolean.TRUE.equals(OAuth2IsPluginEnabled.valueInScope(ConfigKey.Scope.Domain, domainId, true));
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

Passing strictScope = true disables fallback to parent/global scope (it returns null when no domain-scoped value exists). That contradicts the PR goal of 'global fallback' and can unexpectedly disable OAuth in domains that rely on global config. Consider using the default valueInScope(scope, id) (or strictScope = false) for enablement checks where fallback is desired.

Suggested change
return Boolean.TRUE.equals(OAuth2IsPluginEnabled.valueInScope(ConfigKey.Scope.Domain, domainId, true));
return Boolean.TRUE.equals(OAuth2IsPluginEnabled.valueInScope(ConfigKey.Scope.Domain, domainId));

Copilot uses AI. Check for mistakes.
private String provider;

@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
description = "List OAuth providers for a specific domain. Use -1 for global providers only.")
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The parameter is declared as CommandType.UUID but the description instructs using -1, which is not a UUID and may be rejected by request validation/parsing. Consider modeling the global-only filter via a separate boolean/string parameter (or making this parameter a STRING) rather than overloading a UUID-typed field with -1.

Suggested change
description = "List OAuth providers for a specific domain. Use -1 for global providers only.")
description = "List OAuth providers for a specific domain.")

Copilot uses AI. Check for mistakes.
Comment on lines +157 to +170
int totalEnabledCount = responses.size();
if (!domainRequested && !isAuthenticated) {
List<OauthProviderVO> allProviders = _oauth2mgr.listOauthProviders(null, null, null);
for (OauthProviderVO domainProvider : allProviders) {
if (domainProvider.getDomainId() != null && domainProvider.isEnabled()
&& Boolean.TRUE.equals(OAuth2AuthManager.OAuth2IsPluginEnabled.valueInScope(ConfigKey.Scope.Domain, domainProvider.getDomainId(), true))
&& authenticatorPluginNames.contains(domainProvider.getProvider())) {
totalEnabledCount++;
}
}
}

ListResponse<OauthProviderResponse> response = new ListResponse<>();
response.setResponses(responses, resultList.size());
response.setResponses(responses, totalEnabledCount);
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

ListResponse#setResponses(items, count) typically expects count to reflect the number of returned items, not the number of enabled providers across other (non-returned) domains. Returning a count larger than responses.size() can break pagination/clients and is semantically confusing. If the UI needs 'total enabled providers across all domains', consider returning that as a separate field/metric rather than overloading count.

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +113
try {
cmd.setAuthenticators(authenticators);
} catch (AssertionError e) {
// ComponentContext is not available in unit test environment
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

Catching and ignoring AssertionError can mask genuine failures and make tests flaky/less trustworthy. Prefer refactoring setAuthenticators (or the test setup) so it doesn’t depend on ComponentContext/assertions in unit tests, or explicitly skip the test with an assumption when the required context isn’t available.

Copilot uses AI. Check for mistakes.
Comment on lines +240 to +269
public Long resolveDomainId(Map<String, Object[]> params) {
final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID);
if (ArrayUtils.isNotEmpty(domainIdArray)) {
String domainUuid = domainIdArray[0];
if (GLOBAL_DOMAIN_FILTER.equals(domainUuid)) {
return GLOBAL_DOMAIN_ID;
}
DomainVO domain = _domainDao.findByUuid(domainUuid);
if (Objects.nonNull(domain)) {
return domain.getId();
}
}
final String[] domainArray = (String[])params.get(ApiConstants.DOMAIN);
if (ArrayUtils.isNotEmpty(domainArray)) {
String path = domainArray[0];
if (StringUtils.isNotEmpty(path)) {
if (!path.startsWith("/")) {
path = "/" + path;
}
if (!path.endsWith("/")) {
path += "/";
}
DomainVO domain = _domainDao.findDomainByPath(path);
if (Objects.nonNull(domain)) {
return domain.getId();
}
}
}
return null;
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The new resolveDomainId logic adds non-trivial behavior (UUID lookup, special -1 handling, and domain-path normalization). Adding unit tests for these branches would help prevent regressions (e.g., path formatting and the -1 global filter behavior).

Copilot uses AI. Check for mistakes.
@Damans227
Copy link
Contributor Author

@blueorangutan package

@blueorangutan
Copy link

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants