From dfc45c0988419f0da39324ec7eb3edf03df2f847 Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 11:23:43 -0500 Subject: [PATCH 01/24] Add domain_id to oauth_provider table and VO --- .../resources/META-INF/db/schema-42210to42300.sql | 6 ++++++ .../apache/cloudstack/oauth2/vo/OauthProviderVO.java | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index 7024368a1d51..1c4a00c82b7c 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -18,3 +18,9 @@ --; -- Schema upgrade from 4.22.1.0 to 4.23.0.0 --; + +ALTER TABLE `cloud`.`oauth_provider` ADD COLUMN `domain_id` bigint unsigned DEFAULT NULL COMMENT 'NULL for global provider, domain ID for domain-specific' AFTER `redirect_uri`; +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`); \ No newline at end of file diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.java index efd6004e8f97..33937f72ae60 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.java @@ -55,6 +55,9 @@ public class OauthProviderVO implements Identity, InternalIdentity { @Column(name = "redirect_uri") private String redirectUri; + @Column(name = "domain_id") + private Long domainId; + @Column(name = GenericDao.CREATED_COLUMN) private Date created; @@ -118,6 +121,14 @@ public void setSecretKey(String secretKey) { this.secretKey = secretKey; } + public Long getDomainId() { + return domainId; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + public boolean isEnabled() { return enabled; } From fcb47786ce2c36c35b06636f23bb0c2dfeca3a15 Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 11:27:28 -0500 Subject: [PATCH 02/24] Add domain-aware methods to OauthProviderDao --- .../oauth2/dao/OauthProviderDao.java | 8 +++++ .../oauth2/dao/OauthProviderDaoImpl.java | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java index 31738ac75a0f..1127c625f104 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java @@ -19,8 +19,16 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.oauth2.vo.OauthProviderVO; +import java.util.List; + public interface OauthProviderDao extends GenericDao { public OauthProviderVO findByProvider(String provider); + public OauthProviderVO findByProviderAndDomain(String provider, Long domainId); + + public List listByDomain(Long domainId); + + public List listByDomainIncludingGlobal(Long domainId); + } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java index 27eea4d22a6b..6ec7f981dd9e 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java @@ -22,9 +22,13 @@ import com.cloud.utils.db.SearchCriteria; import org.apache.cloudstack.oauth2.vo.OauthProviderVO; +import java.util.List; + public class OauthProviderDaoImpl extends GenericDaoBase implements OauthProviderDao { private final SearchBuilder oauthProviderSearchByName; + private final SearchBuilder oauthProviderSearchByProviderAndDomain; + private final SearchBuilder oauthProviderSearchByDomain; public OauthProviderDaoImpl() { super(); @@ -32,6 +36,15 @@ public OauthProviderDaoImpl() { oauthProviderSearchByName = createSearchBuilder(); oauthProviderSearchByName.and("provider", oauthProviderSearchByName.entity().getProvider(), SearchCriteria.Op.EQ); oauthProviderSearchByName.done(); + + oauthProviderSearchByProviderAndDomain = createSearchBuilder(); + oauthProviderSearchByProviderAndDomain.and("provider", oauthProviderSearchByProviderAndDomain.entity().getProvider(), SearchCriteria.Op.EQ); + oauthProviderSearchByProviderAndDomain.and("domainId", oauthProviderSearchByProviderAndDomain.entity().getDomainId(), SearchCriteria.Op.EQ); + oauthProviderSearchByProviderAndDomain.done(); + + oauthProviderSearchByDomain = createSearchBuilder(); + oauthProviderSearchByDomain.and("domainId", oauthProviderSearchByDomain.entity().getDomainId(), SearchCriteria.Op.EQ); + oauthProviderSearchByDomain.done(); } @Override @@ -41,4 +54,27 @@ public OauthProviderVO findByProvider(String provider) { return findOneBy(sc); } + + @Override + public OauthProviderVO findByProviderAndDomain(String provider, Long domainId) { + SearchCriteria sc = oauthProviderSearchByProviderAndDomain.create(); + sc.setParameters("provider", provider); + sc.setParameters("domainId", domainId); + return findOneBy(sc); + } + + @Override + public List listByDomain(Long domainId) { + SearchCriteria sc = oauthProviderSearchByDomain.create(); + sc.setParameters("domainId", domainId); + return listBy(sc); + } + + @Override + public List listByDomainIncludingGlobal(Long domainId) { + SearchCriteria sc = createSearchCriteria(); + sc.addOr("domainId", SearchCriteria.Op.EQ, domainId); + sc.addOr("domainId", SearchCriteria.Op.NULL); + return listBy(sc); + } } From 85c64e5d90eafd5a1ed89028f32aa62cf0c06017 Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 11:42:32 -0500 Subject: [PATCH 03/24] Add domainId parameter to OAuth provider API commands and response --- .../api/command/ListOAuthProvidersCmd.java | 15 ++++++++++++++- .../api/command/RegisterOAuthProviderCmd.java | 11 ++++++++++- .../api/command/UpdateOAuthProviderCmd.java | 2 +- .../api/response/OauthProviderResponse.java | 17 +++++++++++++++-- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java index abdbf65dbb42..d982d826d8d6 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java @@ -33,6 +33,7 @@ import org.apache.cloudstack.api.auth.APIAuthenticationType; import org.apache.cloudstack.api.auth.APIAuthenticator; import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.auth.UserOAuth2Authenticator; import org.apache.cloudstack.oauth2.OAuth2AuthManager; @@ -59,6 +60,10 @@ public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticat @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider") 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.") + private Long domainId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -70,6 +75,10 @@ public String getProvider() { return provider; } + public Long getDomainId() { + return domainId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -97,6 +106,10 @@ public String authenticate(String command, Map params, HttpSes if (ArrayUtils.isNotEmpty(providerArray)) { provider = providerArray[0]; } + final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID); + if (ArrayUtils.isNotEmpty(domainIdArray)) { + domainId = Long.parseLong(domainIdArray[0]); + } List resultList = _oauth2mgr.listOauthProviders(provider, id); List userOAuth2AuthenticatorPlugins = _oauth2mgr.listUserOAuth2AuthenticationProviders(); @@ -108,7 +121,7 @@ public String authenticate(String command, Map params, HttpSes List responses = new ArrayList<>(); for (OauthProviderVO result : resultList) { OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(), - result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri()); + result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), result.getDomainId()); if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) { r.setEnabled(true); } else { diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java index b31cbde97c52..5e287d68ba7d 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.context.CallContext; import com.cloud.exception.ConcurrentOperationException; @@ -56,6 +57,10 @@ public class RegisterOAuthProviderCmd extends BaseCmd { @Parameter(name = ApiConstants.REDIRECT_URI, type = CommandType.STRING, description = "Redirect URI pre-registered in the specific OAuth provider", required = true) private String redirectUri; + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, + description = "Domain ID for domain-specific OAuth provider. If not provided, registers as global provider") + private Long domainId; + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Any OAuth provider details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].clientsecret=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") protected Map details; @@ -85,6 +90,10 @@ public String getRedirectUri() { return redirectUri; } + public Long getDomainId() { + return domainId; + } + public Map getDetails() { if (MapUtils.isEmpty(details)) { return null; @@ -101,7 +110,7 @@ public void execute() throws ServerApiException, ConcurrentOperationException, E OauthProviderVO provider = _oauth2mgr.registerOauthProvider(this); OauthProviderResponse response = new OauthProviderResponse(provider.getUuid(), provider.getProvider(), - provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri()); + provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri(), provider.getDomainId()); response.setResponseName(getCommandName()); response.setObjectName(ApiConstants.OAUTH_PROVIDER); setResponseObject(response); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java index 1c79b7b144c8..fa05eab911e2 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java @@ -115,7 +115,7 @@ public void execute() { OauthProviderVO result = _oauthMgr.updateOauthProvider(this); if (result != null) { OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(), - result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri()); + result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), result.getDomainId()); List userOAuth2AuthenticatorPlugins = _oauthMgr.listUserOAuth2AuthenticationProviders(); List authenticatorPluginNames = new ArrayList<>(); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java index e0c40bef9b4d..07f70444544f 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java @@ -54,18 +54,23 @@ public class OauthProviderResponse extends BaseResponse { @Param(description = "Redirect URI registered in the OAuth provider") private String redirectUri; + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "Domain ID of the provider (null for global)") + private Long domainId; + @SerializedName(ApiConstants.ENABLED) @Param(description = "Whether the OAuth provider is enabled or not") private boolean enabled; - public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri) { + public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri, Long domainId) { this.id = id; this.provider = provider; this.name = provider; this.description = description; this.clientId = clientId; this.secretKey = secretKey; - this.redirectUri = redirectUri; + this.redirectUri = redirectUri; + this.domainId = domainId; } public String getId() { @@ -117,6 +122,14 @@ public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; } + public Long getDomainId() { + return domainId; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + public String getSecretKey() { return secretKey; } From c46fb9838579c04d7366f1f1d011f430c4e1666a Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 11:50:08 -0500 Subject: [PATCH 04/24] Add domain support to OAuth2AuthManager --- .../cloudstack/oauth2/OAuth2AuthManager.java | 2 +- .../oauth2/OAuth2AuthManagerImpl.java | 22 ++++++++++++++----- .../api/command/ListOAuthProvidersCmd.java | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java index ece012db3a40..3680b3a80c09 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java @@ -53,7 +53,7 @@ public interface OAuth2AuthManager extends PluggableAPIAuthenticator, PluggableS OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd); - List listOauthProviders(String provider, String uuid); + List listOauthProviders(String provider, String uuid, Long domainId); boolean deleteOauthProvider(Long id); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java index b65027d6a249..8612987a4014 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java @@ -139,25 +139,36 @@ public OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd) { String clientId = StringUtils.trim(cmd.getClientId()); String redirectUri = StringUtils.trim(cmd.getRedirectUri()); String secretKey = StringUtils.trim(cmd.getSecretKey()); + Long domainId = cmd.getDomainId(); if (!isOAuthPluginEnabled()) { throw new CloudRuntimeException("OAuth is not enabled, please enable to register"); } - OauthProviderVO providerVO = _oauthProviderDao.findByProvider(provider); + + // Check for existing provider with same name and domain + OauthProviderVO providerVO = _oauthProviderDao.findByProviderAndDomain(provider, domainId); if (providerVO != null) { - throw new CloudRuntimeException(String.format("Provider with the name %s is already registered", provider)); + if (domainId == null) { + throw new CloudRuntimeException(String.format("Global provider with the name %s is already registered", provider)); + } else { + throw new CloudRuntimeException(String.format("Provider with the name %s is already registered for domain %d", provider, domainId)); + } } - return saveOauthProvider(provider, description, clientId, secretKey, redirectUri); + return saveOauthProvider(provider, description, clientId, secretKey, redirectUri, domainId); } @Override - public List listOauthProviders(String provider, String uuid) { + public List listOauthProviders(String provider, String uuid, Long domainId) { List providers; if (uuid != null) { providers = Collections.singletonList(_oauthProviderDao.findByUuid(uuid)); + } else if (StringUtils.isNotBlank(provider) && domainId != null) { + providers = Collections.singletonList(_oauthProviderDao.findByProviderAndDomain(provider, domainId)); } else if (StringUtils.isNotBlank(provider)) { providers = Collections.singletonList(_oauthProviderDao.findByProvider(provider)); + } else if (domainId != null) { + providers = _oauthProviderDao.listByDomainIncludingGlobal(domainId); } else { providers = _oauthProviderDao.listAll(); } @@ -199,7 +210,7 @@ public OauthProviderVO updateOauthProvider(UpdateOAuthProviderCmd cmd) { return _oauthProviderDao.findById(id); } - private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri) { + private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri, Long domainId) { final OauthProviderVO oauthProviderVO = new OauthProviderVO(); oauthProviderVO.setProvider(provider); @@ -207,6 +218,7 @@ private OauthProviderVO saveOauthProvider(String provider, String description, S oauthProviderVO.setClientId(clientId); oauthProviderVO.setSecretKey(secretKey); oauthProviderVO.setRedirectUri(redirectUri); + oauthProviderVO.setDomainId(domainId); oauthProviderVO.setEnabled(true); _oauthProviderDao.persist(oauthProviderVO); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java index d982d826d8d6..fb358d893c80 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java @@ -111,7 +111,7 @@ public String authenticate(String command, Map params, HttpSes domainId = Long.parseLong(domainIdArray[0]); } - List resultList = _oauth2mgr.listOauthProviders(provider, id); + List resultList = _oauth2mgr.listOauthProviders(provider, id, domainId); List userOAuth2AuthenticatorPlugins = _oauth2mgr.listUserOAuth2AuthenticationProviders(); List authenticatorPluginNames = new ArrayList<>(); for (UserOAuth2Authenticator authenticator : userOAuth2AuthenticatorPlugins) { From 4ef100c62a648b17a68bb37618b88dd40b60b1b6 Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 12:04:59 -0500 Subject: [PATCH 05/24] Add domain-aware OAuth verification --- .../auth/UserOAuth2Authenticator.java | 11 +++ .../cloudstack/oauth2/OAuth2AuthManager.java | 2 +- .../oauth2/OAuth2AuthManagerImpl.java | 4 +- .../command/VerifyOAuthCodeAndGetUserCmd.java | 15 +++- .../oauth2/github/GithubOAuth2Provider.java | 90 +++++++++++++++++++ .../oauth2/google/GoogleOAuth2Provider.java | 85 ++++++++++++++++++ 6 files changed, 203 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java b/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java index ee3b98b8a4b6..722063f7aef4 100644 --- a/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java +++ b/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java @@ -44,6 +44,17 @@ public interface UserOAuth2Authenticator extends Adapter { */ String verifyCodeAndFetchEmail(String secretCode); + /** + * Verifies if the logged in user is valid for a specific domain + * @return returns true if its valid user + */ + boolean verifyUser(String email, String secretCode, Long domainId); + + /** + * Verifies the code provided by provider and fetches email for a specific domain + * @return returns email + */ + String verifyCodeAndFetchEmail(String secretCode, Long domainId); /** * Fetches email using the accessToken diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java index 3680b3a80c09..2c2f0da7f058 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java @@ -49,7 +49,7 @@ public interface OAuth2AuthManager extends PluggableAPIAuthenticator, PluggableS */ UserOAuth2Authenticator getUserOAuth2AuthenticationProvider(final String providerName); - String verifyCodeAndFetchEmail(String code, String provider); + String verifyCodeAndFetchEmail(String code, String provider, Long domainId); OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java index 8612987a4014..eb53d0e493c2 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java @@ -125,9 +125,9 @@ protected void initializeUserOAuth2AuthenticationProvidersMap() { } @Override - public String verifyCodeAndFetchEmail(String code, String provider) { + public String verifyCodeAndFetchEmail(String code, String provider, Long domainId) { UserOAuth2Authenticator authenticator = getUserOAuth2AuthenticationProvider(provider); - String email = authenticator.verifyCodeAndFetchEmail(code); + String email = authenticator.verifyCodeAndFetchEmail(code, domainId); return email; } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java index bd49f87d6273..a203db91c63c 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.api.auth.APIAuthenticationType; import org.apache.cloudstack.api.auth.APIAuthenticator; import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.oauth2.OAuth2AuthManager; import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse; @@ -56,6 +57,10 @@ public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuth @Parameter(name = ApiConstants.SECRET_CODE, type = CommandType.STRING, description = "Code that is provided by OAuth provider (Eg. google, github) after successful login") private String secretCode; + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, + description = "Domain ID for domain-specific OAuth provider lookup") + private Long domainId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -68,6 +73,10 @@ public String getSecretCode() { return secretCode; } + public Long getDomainId() { + return domainId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -95,8 +104,12 @@ public String authenticate(String command, Map params, HttpSes if (ArrayUtils.isNotEmpty(providerArray)) { provider = providerArray[0]; } + final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID); + if (ArrayUtils.isNotEmpty(domainIdArray)) { + domainId = Long.parseLong(domainIdArray[0]); + } - String email = _oauth2mgr.verifyCodeAndFetchEmail(secretCode, provider); + String email = _oauth2mgr.verifyCodeAndFetchEmail(secretCode, provider, domainId); if (email != null) { UserResponse response = new UserResponse(); response.setEmail(email); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java index e4a7fae101f0..547f3ba0e65b 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java @@ -176,4 +176,94 @@ public String getUserEmailAddress() throws CloudRuntimeException { private void clearAccessToken() { accessToken = null; } + + @Override + public boolean verifyUser(String email, String secretCode, Long domainId) { + if (StringUtils.isAnyEmpty(email, secretCode)) { + throw new CloudRuntimeException(String.format("Either email or secretcode should not be null/empty")); + } + + OauthProviderVO providerVO = getProviderForDomain(domainId); + if (providerVO == null) { + throw new CloudRuntimeException("Github provider is not registered, so user cannot be verified"); + } + + String verifiedEmail = verifyCodeAndFetchEmail(secretCode, domainId); + if (verifiedEmail == null || !email.equals(verifiedEmail)) { + throw new CloudRuntimeException("Unable to verify the email address with the provided secret"); + } + + clearAccessToken(); + + return true; + } + + @Override + public String verifyCodeAndFetchEmail(String secretCode, Long domainId) { + String accessToken = getAccessToken(secretCode, domainId); + if (accessToken == null) { + return null; + } + return getUserEmailAddress(); + } + + protected String getAccessToken(String secretCode, Long domainId) throws CloudRuntimeException { + OauthProviderVO githubProvider = getProviderForDomain(domainId); + String tokenUrl = "https://github.com/login/oauth/access_token"; + String generatedAccessToken = null; + try { + URL url = new URL(tokenUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setDoOutput(true); + + String jsonParams = "{\"client_id\":\"" + githubProvider.getClientId() + "\",\"client_secret\":\"" + githubProvider.getSecretKey() + "\",\"code\":\"" + secretCode + "\"}"; + + try (OutputStream os = connection.getOutputStream()) { + byte[] input = jsonParams.getBytes("utf-8"); + os.write(input, 0, input.length); + } + + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + String regexPattern = "access_token=([^&]+)"; + Pattern pattern = Pattern.compile(regexPattern); + Matcher matcher = pattern.matcher(response); + if (matcher.find()) { + generatedAccessToken = matcher.group(1); + } else { + throw new CloudRuntimeException("Could not fetch access token from the given code"); + } + } + } else { + throw new CloudRuntimeException("HTTP Request while fetching access token from github failed with error code: " + responseCode); + } + } catch (IOException e) { + throw new CloudRuntimeException(String.format("Error while trying to fetch the github access token : %s", e.getMessage())); + } + + accessToken = generatedAccessToken; + return accessToken; + } + + /** + * Gets OAuth provider config, preferring domain-specific over global + */ + private OauthProviderVO getProviderForDomain(Long domainId) { + OauthProviderVO provider = null; + if (domainId != null) { + provider = _oauthProviderDao.findByProviderAndDomain(getName(), domainId); + } + if (provider == null) { + provider = _oauthProviderDao.findByProviderAndDomain(getName(), null); + } + return provider; + } } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java index 42ed1451ccd5..495faedff55c 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java @@ -136,4 +136,89 @@ protected void clearAccessAndRefreshTokens() { public String getUserEmailAddress() throws CloudRuntimeException { return null; } + + @Override + public boolean verifyUser(String email, String secretCode, Long domainId) { + if (StringUtils.isAnyEmpty(email, secretCode)) { + throw new CloudAuthenticationException("Either email or secret code should not be null/empty"); + } + + OauthProviderVO providerVO = getProviderForDomain(domainId); + if (providerVO == null) { + throw new CloudAuthenticationException("Google provider is not registered, so user cannot be verified"); + } + + String verifiedEmail = verifyCodeAndFetchEmail(secretCode, domainId); + if (verifiedEmail == null || !email.equals(verifiedEmail)) { + throw new CloudRuntimeException("Unable to verify the email address with the provided secret"); + } + clearAccessAndRefreshTokens(); + + return true; + } + + @Override + public String verifyCodeAndFetchEmail(String secretCode, Long domainId) { + OauthProviderVO provider = getProviderForDomain(domainId); + String clientId = provider.getClientId(); + String secret = provider.getSecretKey(); + String redirectURI = provider.getRedirectUri(); + GoogleClientSecrets clientSecrets = new GoogleClientSecrets() + .setWeb(new GoogleClientSecrets.Details() + .setClientId(clientId) + .setClientSecret(secret)); + + NetHttpTransport httpTransport = new NetHttpTransport(); + JsonFactory jsonFactory = new JacksonFactory(); + List scopes = Arrays.asList( + "https://www.googleapis.com/auth/userinfo.profile", + "https://www.googleapis.com/auth/userinfo.email"); + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + httpTransport, jsonFactory, clientSecrets, scopes) + .build(); + + if (StringUtils.isAnyEmpty(accessToken, refreshToken)) { + GoogleTokenResponse tokenResponse = null; + try { + tokenResponse = flow.newTokenRequest(secretCode) + .setRedirectUri(redirectURI) + .execute(); + } catch (IOException e) { + throw new RuntimeException(e); + } + accessToken = tokenResponse.getAccessToken(); + refreshToken = tokenResponse.getRefreshToken(); + } + + GoogleCredential credential = new GoogleCredential.Builder() + .setTransport(httpTransport) + .setJsonFactory(jsonFactory) + .setClientSecrets(clientSecrets) + .build() + .setAccessToken(accessToken) + .setRefreshToken(refreshToken); + + Oauth2 oauth2 = new Oauth2.Builder(httpTransport, jsonFactory, credential).build(); + Userinfo userinfo = null; + try { + userinfo = oauth2.userinfo().get().execute(); + } catch (IOException e) { + throw new CloudRuntimeException(String.format("Failed to fetch the email address with the provided secret: %s" + e.getMessage())); + } + return userinfo.getEmail(); + } + + /** + * Gets OAuth provider config, preferring domain-specific over global + */ + private OauthProviderVO getProviderForDomain(Long domainId) { + OauthProviderVO provider = null; + if (domainId != null) { + provider = _oauthProviderDao.findByProviderAndDomain(getName(), domainId); + } + if (provider == null) { + provider = _oauthProviderDao.findByProviderAndDomain(getName(), null); + } + return provider; + } } From 8e48938370d0a333f5790796b2b6c4d8ef206c4e Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 13:01:29 -0500 Subject: [PATCH 06/24] Add domain support to ListOAuthProvidersCmd and update related tests --- .../api/command/ListOAuthProvidersCmd.java | 18 +++++++++++++++++- .../oauth2/OAuth2AuthManagerImplTest.java | 14 ++++++++------ .../VerifyOAuthCodeAndGetUserCmdTest.java | 4 ++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java index fb358d893c80..8b61d4ce80d8 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java @@ -22,7 +22,9 @@ import java.util.Map; import com.cloud.api.response.ApiResponseSerializer; +import com.cloud.domain.dao.DomainDao; import com.cloud.user.Account; +import com.cloud.utils.component.ComponentContext; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -85,6 +87,8 @@ public Long getDomainId() { OAuth2AuthManager _oauth2mgr; + DomainDao _domainDao; + @Override public long getEntityOwnerId() { return Account.Type.NORMAL.ordinal(); @@ -108,7 +112,15 @@ public String authenticate(String command, Map params, HttpSes } final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID); if (ArrayUtils.isNotEmpty(domainIdArray)) { - domainId = Long.parseLong(domainIdArray[0]); + String domainUuid = domainIdArray[0]; + if ("-1".equals(domainUuid)) { + domainId = -1L; // Special case for global-only filter + } else { + com.cloud.domain.DomainVO domain = _domainDao.findByUuid(domainUuid); + if (domain != null) { + domainId = domain.getId(); + } + } } List resultList = _oauth2mgr.listOauthProviders(provider, id, domainId); @@ -154,5 +166,9 @@ public void setAuthenticators(List authenticators) { if (_oauth2mgr == null) { logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers"); } + _domainDao = (DomainDao) ComponentContext.getComponent(DomainDao.class); + if (_domainDao == null) { + logger.error("Could not get DomainDao component"); + } } } diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java index 3fd5636102ce..60e56085e25c 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java @@ -79,22 +79,24 @@ public void testRegisterOauthProvider() { when(_authManager.isOAuthPluginEnabled()).thenReturn(true); OauthProviderVO providerVO = new OauthProviderVO(); providerVO.setProvider("testProvider"); - when(_authManager._oauthProviderDao.findByProvider(Mockito.anyString())).thenReturn(providerVO); + when(_authManager._oauthProviderDao.findByProviderAndDomain(Mockito.anyString(), Mockito.isNull())).thenReturn(providerVO); when(cmd.getProvider()).thenReturn("testProvider"); + when(cmd.getDomainId()).thenReturn(null); try { _authManager.registerOauthProvider(cmd); Assert.fail("Expected CloudRuntimeException was not thrown"); } catch (CloudRuntimeException e) { - assertEquals("Provider with the name testProvider is already registered", e.getMessage()); + assertEquals("Global provider with the name testProvider is already registered", e.getMessage()); } // Test when provider is github and secret key is not null when(cmd.getSecretKey()).thenReturn("testSecretKey"); providerVO = null; - when(_authManager._oauthProviderDao.findByProvider(Mockito.anyString())).thenReturn(providerVO); + when(_authManager._oauthProviderDao.findByProviderAndDomain(Mockito.anyString(), Mockito.isNull())).thenReturn(providerVO); OauthProviderVO savedProviderVO = new OauthProviderVO(); when(cmd.getProvider()).thenReturn("github"); + when(cmd.getDomainId()).thenReturn(null); when(_authManager._oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenReturn(savedProviderVO); OauthProviderVO result = _authManager.registerOauthProvider(cmd); assertEquals("github", result.getProvider()); @@ -150,17 +152,17 @@ public void testListOauthProviders() { // Test when uuid is not null when(_oauthProviderDao.findByUuid(uuid)).thenReturn(providerVO); - List result = _authManager.listOauthProviders(null, uuid); + List result = _authManager.listOauthProviders(null, uuid, null); assertEquals(providerList, result); // Test when provider is not blank when(_oauthProviderDao.findByProvider(provider)).thenReturn(providerVO); - result = _authManager.listOauthProviders(provider, null); + result = _authManager.listOauthProviders(provider, null, null); assertEquals(providerList, result); // Test when both uuid and provider are null when(_oauthProviderDao.listAll()).thenReturn(providerList); - result = _authManager.listOauthProviders(null, null); + result = _authManager.listOauthProviders(null, null, null); assertEquals(providerList, result); } diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java index 59245a4027aa..fa515daf1da2 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java @@ -71,7 +71,7 @@ public void testAuthenticate() { params.put("secretcode", secretcodeArray); params.put("provider", providerArray); - when(oauth2mgr.verifyCodeAndFetchEmail("secretcode", "provider")).thenReturn("test@example.com"); + when(oauth2mgr.verifyCodeAndFetchEmail("secretcode", "provider", null)).thenReturn("test@example.com"); String response = cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp); @@ -89,7 +89,7 @@ public void testAuthenticateWithInvalidCode() throws Exception { params.put("secretcode", secretcodeArray); params.put("provider", providerArray); - when(oauth2mgr.verifyCodeAndFetchEmail("invalidcode", "provider")).thenReturn(null); + when(oauth2mgr.verifyCodeAndFetchEmail("invalidcode", "provider", null)).thenReturn(null); cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp); } From 9ab26db5db12f0f955fbe1c6424bd081435ce09f Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 14:31:13 -0500 Subject: [PATCH 07/24] fix domain path issue --- .../command/VerifyOAuthCodeAndGetUserCmd.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java index a203db91c63c..e9cfb81b66fc 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java @@ -21,7 +21,10 @@ import java.util.Map; import com.cloud.api.response.ApiResponseSerializer; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.user.Account; +import com.cloud.utils.component.ComponentContext; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -61,6 +64,10 @@ public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuth description = "Domain ID for domain-specific OAuth provider lookup") private Long domainId; + @Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING, + description = "Domain path for domain-specific OAuth provider lookup") + private String domainPath; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -83,6 +90,8 @@ public Long getDomainId() { protected OAuth2AuthManager _oauth2mgr; + DomainDao _domainDao; + @Override public long getEntityOwnerId() { return Account.Type.NORMAL.ordinal(); @@ -108,6 +117,19 @@ public String authenticate(String command, Map params, HttpSes if (ArrayUtils.isNotEmpty(domainIdArray)) { domainId = Long.parseLong(domainIdArray[0]); } + final String[] domainArray = (String[])params.get(ApiConstants.DOMAIN); + if (ArrayUtils.isNotEmpty(domainArray) && domainId == null) { + String path = domainArray[0]; + if (path != null && !"/".equals(path)) { + // Look up domain by path - ensure path starts with / + String fullPath = path.startsWith("/") ? path : "/" + path; + if (!fullPath.endsWith("/")) fullPath = fullPath + "/"; + DomainVO domain = _domainDao.findDomainByPath(fullPath); + if (domain != null) { + domainId = domain.getId(); + } + } + } String email = _oauth2mgr.verifyCodeAndFetchEmail(secretCode, provider, domainId); if (email != null) { @@ -137,5 +159,9 @@ public void setAuthenticators(List authenticators) { if (_oauth2mgr == null) { logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers"); } + _domainDao = (DomainDao) ComponentContext.getComponent(DomainDao.class); + if (_domainDao == null) { + logger.error("Could not get DomainDao component"); + } } } From 3e7fa92a7fef2f4e131d5df03d56c7c2815c90bc Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Thu, 18 Dec 2025 14:31:38 -0500 Subject: [PATCH 08/24] Add domainId support to OAuth provider --- ui/src/config/section/config.js | 6 +++--- ui/src/views/auth/Login.vue | 21 ++++++++++++--------- ui/src/views/dashboard/VerifyOauth.vue | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ui/src/config/section/config.js b/ui/src/config/section/config.js index e190515855e6..adeef3194a54 100644 --- a/ui/src/config/section/config.js +++ b/ui/src/config/section/config.js @@ -79,8 +79,8 @@ export default { icon: 'login-outlined', docHelp: 'adminguide/accounts.html#using-an-ldap-server-for-user-authentication', permission: ['listOauthProvider'], - columns: ['provider', 'enabled', 'description', 'clientid', 'secretkey', 'redirecturi'], - details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi'], + columns: ['provider', 'enabled', 'description', 'clientid', 'secretkey', 'redirecturi', 'domainid'], + details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi', 'domainid'], actions: [ { api: 'registerOauthProvider', @@ -89,7 +89,7 @@ export default { listView: true, dataView: false, args: [ - 'provider', 'description', 'clientid', 'redirecturi', 'secretkey' + 'provider', 'description', 'clientid', 'redirecturi', 'secretkey', 'domainid' ], mapping: { provider: { diff --git a/ui/src/views/auth/Login.vue b/ui/src/views/auth/Login.vue index 24065f47b1aa..e90b97367e6b 100644 --- a/ui/src/views/auth/Login.vue +++ b/ui/src/views/auth/Login.vue @@ -311,6 +311,18 @@ export default { this.form.idp = this.idps[0].id || '' } }) + this.fetchOauthProviders() + postAPI('forgotPassword', {}).then(response => { + this.forgotPasswordEnabled = response.forgotpasswordresponse.enabled + }).catch((err) => { + if (err?.response?.data === null) { + this.forgotPasswordEnabled = true + } else { + this.forgotPasswordEnabled = false + } + }) + }, + fetchOauthProviders () { getAPI('listOauthProvider', {}).then(response => { if (response) { const oauthproviders = response.listoauthproviderresponse.oauthprovider || [] @@ -329,15 +341,6 @@ export default { this.socialLogin = this.googleprovider || this.githubprovider } }) - postAPI('forgotPassword', {}).then(response => { - this.forgotPasswordEnabled = response.forgotpasswordresponse.enabled - }).catch((err) => { - if (err?.response?.data === null) { - this.forgotPasswordEnabled = true - } else { - this.forgotPasswordEnabled = false - } - }) }, // handler async handleUsernameOrEmail (rule, value) { diff --git a/ui/src/views/dashboard/VerifyOauth.vue b/ui/src/views/dashboard/VerifyOauth.vue index a4734aa5127a..a8d9f1c1f0d7 100644 --- a/ui/src/views/dashboard/VerifyOauth.vue +++ b/ui/src/views/dashboard/VerifyOauth.vue @@ -47,7 +47,7 @@ export default { const code = params.get('code') const provider = this.$localStorage.get(OAUTH_PROVIDER) this.state.loginBtn = true - getAPI('verifyOAuthCodeAndGetUser', { provider: provider, secretcode: code }).then(response => { + getAPI('verifyOAuthCodeAndGetUser', { provider: provider, secretcode: code, domain: this.$localStorage.get(OAUTH_DOMAIN) }).then(response => { const email = response.verifyoauthcodeandgetuserresponse.oauthemail.email const loginParams = {} loginParams.email = email From 4a6b28e33d7da02c63b471560a2fac5330a0171c Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:14:22 -0500 Subject: [PATCH 09/24] Return domain name and UUID in OAuth provider API responses using ApiDBUtils --- .../api/command/ListOAuthProvidersCmd.java | 7 ++-- .../api/command/RegisterOAuthProviderCmd.java | 5 ++- .../api/command/UpdateOAuthProviderCmd.java | 6 +++- .../api/response/OauthProviderResponse.java | 32 ++++++++++++++----- .../command/RegisterOAuthProviderCmdTest.java | 21 ++---------- 5 files changed, 41 insertions(+), 30 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java index 8b61d4ce80d8..0af71564682c 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java @@ -22,6 +22,8 @@ import java.util.Map; import com.cloud.api.response.ApiResponseSerializer; +import com.cloud.api.ApiDBUtils; +import com.cloud.domain.Domain; import com.cloud.domain.dao.DomainDao; import com.cloud.user.Account; import com.cloud.utils.component.ComponentContext; @@ -116,7 +118,7 @@ public String authenticate(String command, Map params, HttpSes if ("-1".equals(domainUuid)) { domainId = -1L; // Special case for global-only filter } else { - com.cloud.domain.DomainVO domain = _domainDao.findByUuid(domainUuid); + Domain domain = _domainDao.findByUuid(domainUuid); if (domain != null) { domainId = domain.getId(); } @@ -132,8 +134,9 @@ public String authenticate(String command, Map params, HttpSes } List responses = new ArrayList<>(); for (OauthProviderVO result : resultList) { + Domain domain = result.getDomainId() != null ? ApiDBUtils.findDomainById(result.getDomainId()) : null; OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(), - result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), result.getDomainId()); + result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), domain); if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) { r.setEnabled(true); } else { diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java index 5e287d68ba7d..29b34330a9bf 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java @@ -30,6 +30,8 @@ import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.context.CallContext; +import com.cloud.api.ApiDBUtils; +import com.cloud.domain.Domain; import com.cloud.exception.ConcurrentOperationException; import java.util.Collection; @@ -109,8 +111,9 @@ public Map getDetails() { public void execute() throws ServerApiException, ConcurrentOperationException, EntityExistsException { OauthProviderVO provider = _oauth2mgr.registerOauthProvider(this); + Domain domain = provider.getDomainId() != null ? ApiDBUtils.findDomainById(provider.getDomainId()) : null; OauthProviderResponse response = new OauthProviderResponse(provider.getUuid(), provider.getProvider(), - provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri(), provider.getDomainId()); + provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri(), domain); response.setResponseName(getCommandName()); response.setObjectName(ApiConstants.OAUTH_PROVIDER); setResponseObject(response); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java index fa05eab911e2..17dde648428a 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.oauth2.api.command; +import com.cloud.api.ApiDBUtils; +import com.cloud.domain.Domain; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.auth.UserOAuth2Authenticator; import org.apache.cloudstack.oauth2.OAuth2AuthManager; @@ -63,6 +65,7 @@ public final class UpdateOAuthProviderCmd extends BaseCmd { @Inject OAuth2AuthManager _oauthMgr; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -114,8 +117,9 @@ public ApiCommandResourceType getApiResourceType() { public void execute() { OauthProviderVO result = _oauthMgr.updateOauthProvider(this); if (result != null) { + Domain domain = result.getDomainId() != null ? ApiDBUtils.findDomainById(result.getDomainId()) : null; OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(), - result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), result.getDomainId()); + result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), domain); List userOAuth2AuthenticatorPlugins = _oauthMgr.listUserOAuth2AuthenticationProviders(); List authenticatorPluginNames = new ArrayList<>(); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java index 07f70444544f..d3e94ee71457 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.oauth2.api.response; +import com.cloud.domain.Domain; import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; @@ -55,14 +56,18 @@ public class OauthProviderResponse extends BaseResponse { private String redirectUri; @SerializedName(ApiConstants.DOMAIN_ID) - @Param(description = "Domain ID of the provider (null for global)") - private Long domainId; + @Param(description = "UUID of the domain the provider belongs to (empty for global)") + private String domainUuid; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "name of the domain the provider belongs to (empty for global)") + private String domainName; @SerializedName(ApiConstants.ENABLED) @Param(description = "Whether the OAuth provider is enabled or not") private boolean enabled; - public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri, Long domainId) { + public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri, Domain domain) { this.id = id; this.provider = provider; this.name = provider; @@ -70,7 +75,10 @@ public OauthProviderResponse(String id, String provider, String description, Str this.clientId = clientId; this.secretKey = secretKey; this.redirectUri = redirectUri; - this.domainId = domainId; + if (domain != null) { + this.domainUuid = domain.getUuid(); + this.domainName = domain.getName(); + } } public String getId() { @@ -122,12 +130,20 @@ public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; } - public Long getDomainId() { - return domainId; + public String getDomainUuid() { + return domainUuid; + } + + public void setDomainUuid(String domainUuid) { + this.domainUuid = domainUuid; + } + + public String getDomainName() { + return domainName; } - public void setDomainId(Long domainId) { - this.domainId = domainId; + public void setDomainName(String domainName) { + this.domainName = domainName; } public String getSecretKey() { diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java index c61edd4610c3..4331d4b283db 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java @@ -28,34 +28,19 @@ import org.apache.cloudstack.oauth2.OAuth2AuthManager; import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse; import org.apache.cloudstack.oauth2.vo.OauthProviderVO; -import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.junit.MockitoJUnitRunner; -@RunWith(MockitoJUnitRunner.class) public class RegisterOAuthProviderCmdTest { - @Mock private OAuth2AuthManager _oauth2mgr; - - @InjectMocks private RegisterOAuthProviderCmd _cmd; - private AutoCloseable closeable; - @Before public void setUp() throws Exception { - closeable = MockitoAnnotations.openMocks(this); - } - - @After - public void tearDown() throws Exception { - closeable.close(); + _oauth2mgr = mock(OAuth2AuthManager.class); + _cmd = new RegisterOAuthProviderCmd(); + _cmd._oauth2mgr = _oauth2mgr; } @Test From 3ab3ca92f144d34f20b5e84b2995e6280c37ec69 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:49:55 -0500 Subject: [PATCH 10/24] Refactor domain ID resolution in VerifyOAuthCodeAndGetUserCmd to improve code clarity --- .../command/VerifyOAuthCodeAndGetUserCmd.java | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java index e9cfb81b66fc..c0369512d335 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java @@ -113,23 +113,7 @@ public String authenticate(String command, Map params, HttpSes if (ArrayUtils.isNotEmpty(providerArray)) { provider = providerArray[0]; } - final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID); - if (ArrayUtils.isNotEmpty(domainIdArray)) { - domainId = Long.parseLong(domainIdArray[0]); - } - final String[] domainArray = (String[])params.get(ApiConstants.DOMAIN); - if (ArrayUtils.isNotEmpty(domainArray) && domainId == null) { - String path = domainArray[0]; - if (path != null && !"/".equals(path)) { - // Look up domain by path - ensure path starts with / - String fullPath = path.startsWith("/") ? path : "/" + path; - if (!fullPath.endsWith("/")) fullPath = fullPath + "/"; - DomainVO domain = _domainDao.findDomainByPath(fullPath); - if (domain != null) { - domainId = domain.getId(); - } - } - } + domainId = resolveDomainId(params); String email = _oauth2mgr.verifyCodeAndFetchEmail(secretCode, provider, domainId); if (email != null) { @@ -144,6 +128,33 @@ public String authenticate(String command, Map params, HttpSes throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to verify the code provided"); } + private Long resolveDomainId(Map params) { + final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID); + if (ArrayUtils.isNotEmpty(domainIdArray)) { + DomainVO domain = _domainDao.findByUuid(domainIdArray[0]); + if (domain != null) { + return domain.getId(); + } + } + final String[] domainArray = (String[])params.get(ApiConstants.DOMAIN); + if (ArrayUtils.isNotEmpty(domainArray)) { + String path = domainArray[0]; + if (path != null) { + if (!path.startsWith("/")) { + path = "/" + path; + } + if (!path.endsWith("/")) { + path += "/"; + } + DomainVO domain = _domainDao.findDomainByPath(path); + if (domain != null) { + return domain.getId(); + } + } + } + return null; + } + @Override public APIAuthenticationType getAPIType() { return null; From bb6f137347106fd06367d9c66e254065d712a16b Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:24:47 -0500 Subject: [PATCH 11/24] Enhance OAuth2 plugin support for domain-level configuration and authentication checks --- .../cloudstack/oauth2/OAuth2AuthManager.java | 2 +- .../cloudstack/oauth2/OAuth2AuthManagerImpl.java | 8 ++++---- .../cloudstack/oauth2/OAuth2UserAuthenticator.java | 6 +++--- .../oauth2/api/command/ListOAuthProvidersCmd.java | 2 +- .../api/command/OauthLoginAPIAuthenticatorCmd.java | 14 +++++++++++--- .../oauth2/api/command/UpdateOAuthProviderCmd.java | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java index 2c2f0da7f058..5e955dc7467b 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java @@ -30,7 +30,7 @@ public interface OAuth2AuthManager extends PluggableAPIAuthenticator, PluggableService { public static ConfigKey OAuth2IsPluginEnabled = new ConfigKey("Advanced", Boolean.class, "oauth2.enabled", "false", - "Indicates whether OAuth plugin is enabled or not", false); + "Indicates whether OAuth plugin is enabled or not. Can be configured at domain level.", true, ConfigKey.Scope.Domain); public static final ConfigKey OAuth2Plugins = new ConfigKey("Advanced", String.class, "oauth2.plugins", "google,github", "List of OAuth plugins", true); public static final ConfigKey OAuth2PluginsExclude = new ConfigKey("Advanced", String.class, "oauth2.plugins.exclude", "", diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java index eb53d0e493c2..d0655b13efda 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java @@ -64,7 +64,7 @@ public List> getAuthCommands() { @Override public boolean start() { - if (isOAuthPluginEnabled()) { + if (isOAuthPluginEnabled(null)) { logger.info("OAUTH plugin loaded"); initializeUserOAuth2AuthenticationProvidersMap(); } else { @@ -73,8 +73,8 @@ public boolean start() { return true; } - protected boolean isOAuthPluginEnabled() { - return OAuth2IsPluginEnabled.value(); + protected boolean isOAuthPluginEnabled(Long domainId) { + return OAuth2IsPluginEnabled.valueIn(domainId); } @Override @@ -141,7 +141,7 @@ public OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd) { String secretKey = StringUtils.trim(cmd.getSecretKey()); Long domainId = cmd.getDomainId(); - if (!isOAuthPluginEnabled()) { + if (!isOAuthPluginEnabled(domainId)) { throw new CloudRuntimeException("OAuth is not enabled, please enable to register"); } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java index dde50c8bb34d..d3cd72563563 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java @@ -49,7 +49,7 @@ public Pair authenticate(String username, logger.debug("Trying OAuth2 auth for user: " + username); } - if (!isOAuthPluginEnabled()) { + if (!isOAuthPluginEnabled(domainId)) { logger.debug("OAuth2 plugin is disabled"); return new Pair(false, null); } else if (requestParameters == null) { @@ -89,7 +89,7 @@ public String encode(String password) { return null; } - protected boolean isOAuthPluginEnabled() { - return OAuth2IsPluginEnabled.value(); + protected boolean isOAuthPluginEnabled(Long domainId) { + return OAuth2IsPluginEnabled.valueIn(domainId); } } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java index 0af71564682c..fc596474d217 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java @@ -137,7 +137,7 @@ public String authenticate(String command, Map params, HttpSes Domain domain = result.getDomainId() != null ? ApiDBUtils.findDomainById(result.getDomainId()) : null; OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(), result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), domain); - if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) { + if (OAuth2AuthManager.OAuth2IsPluginEnabled.valueIn(result.getDomainId()) && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) { r.setEnabled(true); } else { r.setEnabled(false); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java index f9a1d10d3526..02bca8940499 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.oauth2.api.command; +import java.util.Objects; + import com.cloud.api.ApiServlet; import com.cloud.domain.Domain; import com.cloud.user.User; @@ -115,9 +117,6 @@ public void execute() throws ServerApiException { @Override public String authenticate(String command, Map params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException { - if (!OAuth2IsPluginEnabled.value()) { - throw new CloudAuthenticationException("OAuth is not enabled in CloudStack, users cannot login using OAuth"); - } final String[] provider = (String[])params.get(ApiConstants.PROVIDER); final String[] emailArray = (String[])params.get(ApiConstants.EMAIL); final String[] secretCodeArray = (String[])params.get(ApiConstants.SECRET_CODE); @@ -133,6 +132,15 @@ public String authenticate(String command, Map params, HttpSes final String[] domainName = (String[])params.get(ApiConstants.DOMAIN); String domain = getDomainName(auditTrailSb, domainName); + final Domain userDomain = _domainService.findDomainByIdOrPath(domainId, domain); + if (Objects.nonNull(userDomain)) { + domainId = userDomain.getId(); + } + + if (!OAuth2IsPluginEnabled.valueIn(domainId)) { + throw new CloudAuthenticationException("OAuth is not enabled, users cannot login using OAuth"); + } + return doOauthAuthentication(session, domainId, domain, email, params, remoteAddress, responseType, auditTrailSb); } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java index 17dde648428a..07412c17dbb2 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java @@ -127,7 +127,7 @@ public void execute() { String name = authenticator.getName(); authenticatorPluginNames.add(name); } - if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) { + if (OAuth2AuthManager.OAuth2IsPluginEnabled.valueIn(result.getDomainId()) && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) { r.setEnabled(true); } else { r.setEnabled(false); From 764495b9d6f762b9d8be3bf5c1d01ba0c984ce43 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:15:57 -0500 Subject: [PATCH 12/24] Update OAuth2 tests and VerifyOAuthCodeAndGetUserCmdTest --- .../cloudstack/oauth2/OAuth2AuthManagerImplTest.java | 8 ++++---- .../cloudstack/oauth2/OAuth2UserAuthenticatorTest.java | 2 +- .../api/command/VerifyOAuthCodeAndGetUserCmdTest.java | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java index 60e56085e25c..d45276cf004f 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java @@ -66,7 +66,7 @@ public void tearDown() throws Exception { @Test public void testRegisterOauthProvider() { - when(_authManager.isOAuthPluginEnabled()).thenReturn(false); + when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(false); RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class); try { _authManager.registerOauthProvider(cmd); @@ -76,7 +76,7 @@ public void testRegisterOauthProvider() { } // Test when provider is already registered - when(_authManager.isOAuthPluginEnabled()).thenReturn(true); + when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true); OauthProviderVO providerVO = new OauthProviderVO(); providerVO.setProvider("testProvider"); when(_authManager._oauthProviderDao.findByProviderAndDomain(Mockito.anyString(), Mockito.isNull())).thenReturn(providerVO); @@ -180,12 +180,12 @@ public void testGetCommands() { @Test public void testStart() { - when(_authManager.isOAuthPluginEnabled()).thenReturn(true); + when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true); doNothing().when(_authManager).initializeUserOAuth2AuthenticationProvidersMap(); boolean result = _authManager.start(); assertTrue(result); - when(_authManager.isOAuthPluginEnabled()).thenReturn(false); + when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(false); result = _authManager.start(); assertTrue(result); } diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java index d1c1889ba999..5cb7d1a7e226 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java @@ -69,7 +69,7 @@ public class OAuth2UserAuthenticatorTest { @Before public void setUp() { closeable = MockitoAnnotations.openMocks(this); - doReturn(true).when(authenticator).isOAuthPluginEnabled(); + doReturn(true).when(authenticator).isOAuthPluginEnabled(anyLong()); } @After diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java index fa515daf1da2..a78cb81c7cc0 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java @@ -102,7 +102,11 @@ public void testSetAuthenticators() { authenticators.add(mock(PluggableAPIAuthenticator.class)); authenticators.add(oauth2mgr); authenticators.add(null); - cmd.setAuthenticators(authenticators); + try { + cmd.setAuthenticators(authenticators); + } catch (AssertionError e) { + // ComponentContext is not available in unit test environment + } Assert.assertEquals(oauth2mgr, cmd._oauth2mgr); } } From 0ec6bcc4a11e271f70b0e42ee22b6733aceed769 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:21:27 -0500 Subject: [PATCH 13/24] Add method to find OAuth provider by domain with global fallback --- .../cloudstack/oauth2/dao/OauthProviderDao.java | 2 ++ .../oauth2/dao/OauthProviderDaoImpl.java | 12 ++++++++++++ .../oauth2/github/GithubOAuth2Provider.java | 17 ++--------------- .../oauth2/google/GoogleOAuth2Provider.java | 17 ++--------------- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java index 1127c625f104..e6bd592babb1 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java @@ -31,4 +31,6 @@ public interface OauthProviderDao extends GenericDao { public List listByDomainIncludingGlobal(Long domainId); + public OauthProviderVO findByProviderAndDomainWithGlobalFallback(String provider, Long domainId); + } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java index 6ec7f981dd9e..71d89be0e88d 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java @@ -77,4 +77,16 @@ public List listByDomainIncludingGlobal(Long domainId) { sc.addOr("domainId", SearchCriteria.Op.NULL); return listBy(sc); } + + @Override + public OauthProviderVO findByProviderAndDomainWithGlobalFallback(String provider, Long domainId) { + OauthProviderVO providerVO = null; + if (domainId != null) { + providerVO = findByProviderAndDomain(provider, domainId); + } + if (providerVO == null) { + providerVO = findByProviderAndDomain(provider, null); + } + return providerVO; + } } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java index 547f3ba0e65b..9a88e01107e4 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java @@ -183,7 +183,7 @@ public boolean verifyUser(String email, String secretCode, Long domainId) { throw new CloudRuntimeException(String.format("Either email or secretcode should not be null/empty")); } - OauthProviderVO providerVO = getProviderForDomain(domainId); + OauthProviderVO providerVO = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId); if (providerVO == null) { throw new CloudRuntimeException("Github provider is not registered, so user cannot be verified"); } @@ -208,7 +208,7 @@ public String verifyCodeAndFetchEmail(String secretCode, Long domainId) { } protected String getAccessToken(String secretCode, Long domainId) throws CloudRuntimeException { - OauthProviderVO githubProvider = getProviderForDomain(domainId); + OauthProviderVO githubProvider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId); String tokenUrl = "https://github.com/login/oauth/access_token"; String generatedAccessToken = null; try { @@ -253,17 +253,4 @@ protected String getAccessToken(String secretCode, Long domainId) throws CloudRu return accessToken; } - /** - * Gets OAuth provider config, preferring domain-specific over global - */ - private OauthProviderVO getProviderForDomain(Long domainId) { - OauthProviderVO provider = null; - if (domainId != null) { - provider = _oauthProviderDao.findByProviderAndDomain(getName(), domainId); - } - if (provider == null) { - provider = _oauthProviderDao.findByProviderAndDomain(getName(), null); - } - return provider; - } } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java index 495faedff55c..f46613ec3f7c 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java @@ -143,7 +143,7 @@ public boolean verifyUser(String email, String secretCode, Long domainId) { throw new CloudAuthenticationException("Either email or secret code should not be null/empty"); } - OauthProviderVO providerVO = getProviderForDomain(domainId); + OauthProviderVO providerVO = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId); if (providerVO == null) { throw new CloudAuthenticationException("Google provider is not registered, so user cannot be verified"); } @@ -159,7 +159,7 @@ public boolean verifyUser(String email, String secretCode, Long domainId) { @Override public String verifyCodeAndFetchEmail(String secretCode, Long domainId) { - OauthProviderVO provider = getProviderForDomain(domainId); + OauthProviderVO provider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId); String clientId = provider.getClientId(); String secret = provider.getSecretKey(); String redirectURI = provider.getRedirectUri(); @@ -208,17 +208,4 @@ public String verifyCodeAndFetchEmail(String secretCode, Long domainId) { return userinfo.getEmail(); } - /** - * Gets OAuth provider config, preferring domain-specific over global - */ - private OauthProviderVO getProviderForDomain(Long domainId) { - OauthProviderVO provider = null; - if (domainId != null) { - provider = _oauthProviderDao.findByProviderAndDomain(getName(), domainId); - } - if (provider == null) { - provider = _oauthProviderDao.findByProviderAndDomain(getName(), null); - } - return provider; - } } From 9609ef095fa701896d5aec296c874976c6363153 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:35:43 -0500 Subject: [PATCH 14/24] Update OAuth provider configuration to use 'domain' instead of 'domainid' in columns and details --- ui/src/config/section/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/config/section/config.js b/ui/src/config/section/config.js index adeef3194a54..ff039a54c544 100644 --- a/ui/src/config/section/config.js +++ b/ui/src/config/section/config.js @@ -79,8 +79,8 @@ export default { icon: 'login-outlined', docHelp: 'adminguide/accounts.html#using-an-ldap-server-for-user-authentication', permission: ['listOauthProvider'], - columns: ['provider', 'enabled', 'description', 'clientid', 'secretkey', 'redirecturi', 'domainid'], - details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi', 'domainid'], + columns: ['provider', 'enabled', 'description', 'clientid', 'secretkey', 'redirecturi', 'domain'], + details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi', 'domain'], actions: [ { api: 'registerOauthProvider', From 65ccef178cf70ebebb150e0618ce06f448cdff14 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:17:21 -0500 Subject: [PATCH 15/24] Refactor OAuth provider methods to support domain-level queries and enhance user verification --- .../oauth2/OAuth2AuthManagerImpl.java | 2 +- .../oauth2/OAuth2UserAuthenticator.java | 3 +- .../oauth2/dao/OauthProviderDao.java | 4 -- .../oauth2/dao/OauthProviderDaoImpl.java | 25 ------- .../oauth2/github/GithubOAuth2Provider.java | 70 +------------------ .../oauth2/google/GoogleOAuth2Provider.java | 65 +---------------- .../oauth2/OAuth2AuthManagerImplTest.java | 2 +- .../google/GoogleOAuth2ProviderTest.java | 8 +-- 8 files changed, 12 insertions(+), 167 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java index d0655b13efda..89474cb21073 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java @@ -166,7 +166,7 @@ public List listOauthProviders(String provider, String uuid, Lo } else if (StringUtils.isNotBlank(provider) && domainId != null) { providers = Collections.singletonList(_oauthProviderDao.findByProviderAndDomain(provider, domainId)); } else if (StringUtils.isNotBlank(provider)) { - providers = Collections.singletonList(_oauthProviderDao.findByProvider(provider)); + providers = Collections.singletonList(_oauthProviderDao.findByProviderAndDomain(provider, null)); } else if (domainId != null) { providers = _oauthProviderDao.listByDomainIncludingGlobal(domainId); } else { diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java index d3cd72563563..9ad6db9e1918 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java @@ -30,6 +30,7 @@ import javax.inject.Inject; import java.util.Map; +import java.util.Objects; import static org.apache.cloudstack.oauth2.OAuth2AuthManager.OAuth2IsPluginEnabled; @@ -76,7 +77,7 @@ public Pair authenticate(String username, String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]); UserOAuth2Authenticator authenticator = userOAuth2mgr.getUserOAuth2AuthenticationProvider(oauthProvider); - if (user != null && authenticator.verifyUser(email, secretCode)) { + if (Objects.nonNull(user) && authenticator.verifyUser(email, secretCode, domainId)) { return new Pair(true, null); } } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java index e6bd592babb1..3206e19b5065 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java @@ -23,12 +23,8 @@ public interface OauthProviderDao extends GenericDao { - public OauthProviderVO findByProvider(String provider); - public OauthProviderVO findByProviderAndDomain(String provider, Long domainId); - public List listByDomain(Long domainId); - public List listByDomainIncludingGlobal(Long domainId); public OauthProviderVO findByProviderAndDomainWithGlobalFallback(String provider, Long domainId); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java index 71d89be0e88d..0818152f0208 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java @@ -26,33 +26,15 @@ public class OauthProviderDaoImpl extends GenericDaoBase implements OauthProviderDao { - private final SearchBuilder oauthProviderSearchByName; private final SearchBuilder oauthProviderSearchByProviderAndDomain; - private final SearchBuilder oauthProviderSearchByDomain; public OauthProviderDaoImpl() { super(); - oauthProviderSearchByName = createSearchBuilder(); - oauthProviderSearchByName.and("provider", oauthProviderSearchByName.entity().getProvider(), SearchCriteria.Op.EQ); - oauthProviderSearchByName.done(); - oauthProviderSearchByProviderAndDomain = createSearchBuilder(); oauthProviderSearchByProviderAndDomain.and("provider", oauthProviderSearchByProviderAndDomain.entity().getProvider(), SearchCriteria.Op.EQ); oauthProviderSearchByProviderAndDomain.and("domainId", oauthProviderSearchByProviderAndDomain.entity().getDomainId(), SearchCriteria.Op.EQ); oauthProviderSearchByProviderAndDomain.done(); - - oauthProviderSearchByDomain = createSearchBuilder(); - oauthProviderSearchByDomain.and("domainId", oauthProviderSearchByDomain.entity().getDomainId(), SearchCriteria.Op.EQ); - oauthProviderSearchByDomain.done(); - } - - @Override - public OauthProviderVO findByProvider(String provider) { - SearchCriteria sc = oauthProviderSearchByName.create(); - sc.setParameters("provider", provider); - - return findOneBy(sc); } @Override @@ -63,13 +45,6 @@ public OauthProviderVO findByProviderAndDomain(String provider, Long domainId) { return findOneBy(sc); } - @Override - public List listByDomain(Long domainId) { - SearchCriteria sc = oauthProviderSearchByDomain.create(); - sc.setParameters("domainId", domainId); - return listBy(sc); - } - @Override public List listByDomainIncludingGlobal(Long domainId) { SearchCriteria sc = createSearchCriteria(); diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java index 9a88e01107e4..dc8bc63fd410 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java @@ -55,78 +55,12 @@ public String getDescription() { @Override public boolean verifyUser(String email, String secretCode) { - if (StringUtils.isAnyEmpty(email, secretCode)) { - throw new CloudRuntimeException(String.format("Either email or secretcode should not be null/empty")); - } - - OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName()); - if (providerVO == null) { - throw new CloudRuntimeException("Github provider is not registered, so user cannot be verified"); - } - - String verifiedEmail = getUserEmailAddress(); - if (verifiedEmail == null || !email.equals(verifiedEmail)) { - throw new CloudRuntimeException("Unable to verify the email address with the provided secret"); - } - - clearAccessToken(); - - return true; + return verifyUser(email, secretCode, null); } @Override public String verifyCodeAndFetchEmail(String secretCode) { - String accessToken = getAccessToken(secretCode); - if (accessToken == null) { - return null; - } - return getUserEmailAddress(); - } - - protected String getAccessToken(String secretCode) throws CloudRuntimeException { - OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName()); - String tokenUrl = "https://github.com/login/oauth/access_token"; - String generatedAccessToken = null; - try { - URL url = new URL(tokenUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setDoOutput(true); - - String jsonParams = "{\"client_id\":\"" + githubProvider.getClientId() + "\",\"client_secret\":\"" + githubProvider.getSecretKey() + "\",\"code\":\"" + secretCode + "\"}"; - - try (OutputStream os = connection.getOutputStream()) { - byte[] input = jsonParams.getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String inputLine; - StringBuilder response = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); - } - String regexPattern = "access_token=([^&]+)"; - Pattern pattern = Pattern.compile(regexPattern); - Matcher matcher = pattern.matcher(response); - if (matcher.find()) { - generatedAccessToken = matcher.group(1); - } else { - throw new CloudRuntimeException("Could not fetch access token from the given code"); - } - } - } else { - throw new CloudRuntimeException("HTTP Request while fetching access token from github failed with error code: " + responseCode); - } - } catch (IOException e) { - throw new CloudRuntimeException(String.format("Error while trying to fetch the github access token : %s", e.getMessage())); - } - - accessToken = generatedAccessToken; - return accessToken; + return verifyCodeAndFetchEmail(secretCode, null); } public String getUserEmailAddress() throws CloudRuntimeException { diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java index f46613ec3f7c..110046a1251a 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java @@ -58,73 +58,12 @@ public String getDescription() { @Override public boolean verifyUser(String email, String secretCode) { - if (StringUtils.isAnyEmpty(email, secretCode)) { - throw new CloudAuthenticationException("Either email or secret code should not be null/empty"); - } - - OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName()); - if (providerVO == null) { - throw new CloudAuthenticationException("Google provider is not registered, so user cannot be verified"); - } - - String verifiedEmail = verifyCodeAndFetchEmail(secretCode); - if (verifiedEmail == null || !email.equals(verifiedEmail)) { - throw new CloudRuntimeException("Unable to verify the email address with the provided secret"); - } - clearAccessAndRefreshTokens(); - - return true; + return verifyUser(email, secretCode, null); } @Override public String verifyCodeAndFetchEmail(String secretCode) { - OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName()); - String clientId = githubProvider.getClientId(); - String secret = githubProvider.getSecretKey(); - String redirectURI = githubProvider.getRedirectUri(); - GoogleClientSecrets clientSecrets = new GoogleClientSecrets() - .setWeb(new GoogleClientSecrets.Details() - .setClientId(clientId) - .setClientSecret(secret)); - - NetHttpTransport httpTransport = new NetHttpTransport(); - JsonFactory jsonFactory = new JacksonFactory(); - List scopes = Arrays.asList( - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/userinfo.email"); - GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( - httpTransport, jsonFactory, clientSecrets, scopes) - .build(); - - if (StringUtils.isAnyEmpty(accessToken, refreshToken)) { - GoogleTokenResponse tokenResponse = null; - try { - tokenResponse = flow.newTokenRequest(secretCode) - .setRedirectUri(redirectURI) - .execute(); - } catch (IOException e) { - throw new RuntimeException(e); - } - accessToken = tokenResponse.getAccessToken(); - refreshToken = tokenResponse.getRefreshToken(); - } - - GoogleCredential credential = new GoogleCredential.Builder() - .setTransport(httpTransport) - .setJsonFactory(jsonFactory) - .setClientSecrets(clientSecrets) - .build() - .setAccessToken(accessToken) - .setRefreshToken(refreshToken); - - Oauth2 oauth2 = new Oauth2.Builder(httpTransport, jsonFactory, credential).build(); - Userinfo userinfo = null; - try { - userinfo = oauth2.userinfo().get().execute(); - } catch (IOException e) { - throw new CloudRuntimeException(String.format("Failed to fetch the email address with the provided secret: %s" + e.getMessage())); - } - return userinfo.getEmail(); + return verifyCodeAndFetchEmail(secretCode, null); } protected void clearAccessAndRefreshTokens() { diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java index d45276cf004f..0a4fe43cdf7f 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java @@ -156,7 +156,7 @@ public void testListOauthProviders() { assertEquals(providerList, result); // Test when provider is not blank - when(_oauthProviderDao.findByProvider(provider)).thenReturn(providerVO); + when(_oauthProviderDao.findByProviderAndDomain(provider, null)).thenReturn(providerVO); result = _authManager.listOauthProviders(provider, null, null); assertEquals(providerList, result); diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java index fa8a5a7c03cf..1f4a4eb363c8 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java @@ -74,14 +74,14 @@ public void testVerifyUserWithNullSecretCode() { @Test(expected = CloudAuthenticationException.class) public void testVerifyUserWithUnregisteredProvider() { - when(_oauthProviderDao.findByProvider(anyString())).thenReturn(null); + when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(null); _googleOAuth2Provider.verifyUser("email@example.com", "secretCode"); } @Test(expected = CloudRuntimeException.class) public void testVerifyUserWithInvalidSecretCode() throws IOException { OauthProviderVO providerVO = mock(OauthProviderVO.class); - when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO); + when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO); when(providerVO.getProvider()).thenReturn("testProvider"); when(providerVO.getSecretKey()).thenReturn("testSecret"); when(providerVO.getClientId()).thenReturn("testClientid"); @@ -105,7 +105,7 @@ public void testVerifyUserWithInvalidSecretCode() throws IOException { @Test(expected = CloudRuntimeException.class) public void testVerifyUserWithMismatchedEmail() throws IOException { OauthProviderVO providerVO = mock(OauthProviderVO.class); - when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO); + when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO); when(providerVO.getProvider()).thenReturn("testProvider"); when(providerVO.getSecretKey()).thenReturn("testSecret"); when(providerVO.getClientId()).thenReturn("testClientid"); @@ -129,7 +129,7 @@ public void testVerifyUserWithMismatchedEmail() throws IOException { @Test public void testVerifyUserEmail() throws IOException { OauthProviderVO providerVO = mock(OauthProviderVO.class); - when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO); + when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO); when(providerVO.getProvider()).thenReturn("testProvider"); when(providerVO.getSecretKey()).thenReturn("testSecret"); when(providerVO.getClientId()).thenReturn("testClientid"); From e3da6795e49b27d6f60414563e022dca6a2886c2 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:17:26 -0500 Subject: [PATCH 16/24] Add caching for access token retrieval in GithubOAuth2Provider --- .../apache/cloudstack/oauth2/github/GithubOAuth2Provider.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java index dc8bc63fd410..608fc01a491a 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java @@ -142,6 +142,9 @@ public String verifyCodeAndFetchEmail(String secretCode, Long domainId) { } protected String getAccessToken(String secretCode, Long domainId) throws CloudRuntimeException { + if (accessToken != null) { + return accessToken; + } OauthProviderVO githubProvider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId); String tokenUrl = "https://github.com/login/oauth/access_token"; String generatedAccessToken = null; From 0e32a60ef0aa7cec86e74410fd2976e797c1d2a0 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:20:55 -0500 Subject: [PATCH 17/24] Refactor access token checks in GithubOAuth2Provider to use StringUtils for improved readability and consistency --- .../cloudstack/oauth2/github/GithubOAuth2Provider.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java index 608fc01a491a..824820e84dfe 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java @@ -64,7 +64,7 @@ public String verifyCodeAndFetchEmail(String secretCode) { } public String getUserEmailAddress() throws CloudRuntimeException { - if (accessToken == null) { + if (StringUtils.isEmpty(accessToken)) { throw new CloudRuntimeException("Access Token not found to fetch the email address"); } @@ -123,7 +123,7 @@ public boolean verifyUser(String email, String secretCode, Long domainId) { } String verifiedEmail = verifyCodeAndFetchEmail(secretCode, domainId); - if (verifiedEmail == null || !email.equals(verifiedEmail)) { + if (StringUtils.isEmpty(verifiedEmail) || !email.equals(verifiedEmail)) { throw new CloudRuntimeException("Unable to verify the email address with the provided secret"); } @@ -135,14 +135,14 @@ public boolean verifyUser(String email, String secretCode, Long domainId) { @Override public String verifyCodeAndFetchEmail(String secretCode, Long domainId) { String accessToken = getAccessToken(secretCode, domainId); - if (accessToken == null) { + if (StringUtils.isEmpty(accessToken)) { return null; } return getUserEmailAddress(); } protected String getAccessToken(String secretCode, Long domainId) throws CloudRuntimeException { - if (accessToken != null) { + if (StringUtils.isNotEmpty(accessToken)) { return accessToken; } OauthProviderVO githubProvider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId); From 23c41322c0ad30d04a5e5f35fcf8ae0dbbef18aa Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 19:22:39 -0500 Subject: [PATCH 18/24] Refactor null checks to use utility for improved readability and consistency --- .../oauth2/api/command/ListOAuthProvidersCmd.java | 5 +++-- .../api/command/VerifyOAuthCodeAndGetUserCmd.java | 10 ++++++---- .../oauth2/api/response/OauthProviderResponse.java | 4 +++- .../cloudstack/oauth2/dao/OauthProviderDaoImpl.java | 5 +++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java index fc596474d217..67b00963d705 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import com.cloud.api.response.ApiResponseSerializer; import com.cloud.api.ApiDBUtils; @@ -119,7 +120,7 @@ public String authenticate(String command, Map params, HttpSes domainId = -1L; // Special case for global-only filter } else { Domain domain = _domainDao.findByUuid(domainUuid); - if (domain != null) { + if (Objects.nonNull(domain)) { domainId = domain.getId(); } } @@ -170,7 +171,7 @@ public void setAuthenticators(List authenticators) { logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers"); } _domainDao = (DomainDao) ComponentContext.getComponent(DomainDao.class); - if (_domainDao == null) { + if (Objects.isNull(_domainDao)) { logger.error("Could not get DomainDao component"); } } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java index c0369512d335..174721f2169f 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java @@ -19,6 +19,7 @@ import java.net.InetAddress; import java.util.List; import java.util.Map; +import java.util.Objects; import com.cloud.api.response.ApiResponseSerializer; import com.cloud.domain.DomainVO; @@ -40,6 +41,7 @@ import org.apache.cloudstack.oauth2.OAuth2AuthManager; import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse; import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -132,14 +134,14 @@ private Long resolveDomainId(Map params) { final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID); if (ArrayUtils.isNotEmpty(domainIdArray)) { DomainVO domain = _domainDao.findByUuid(domainIdArray[0]); - if (domain != null) { + if (Objects.nonNull(domain)) { return domain.getId(); } } final String[] domainArray = (String[])params.get(ApiConstants.DOMAIN); if (ArrayUtils.isNotEmpty(domainArray)) { String path = domainArray[0]; - if (path != null) { + if (StringUtils.isNotEmpty(path)) { if (!path.startsWith("/")) { path = "/" + path; } @@ -147,7 +149,7 @@ private Long resolveDomainId(Map params) { path += "/"; } DomainVO domain = _domainDao.findDomainByPath(path); - if (domain != null) { + if (Objects.nonNull(domain)) { return domain.getId(); } } @@ -171,7 +173,7 @@ public void setAuthenticators(List authenticators) { logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers"); } _domainDao = (DomainDao) ComponentContext.getComponent(DomainDao.class); - if (_domainDao == null) { + if (Objects.isNull(_domainDao)) { logger.error("Could not get DomainDao component"); } } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java index d3e94ee71457..2d50c341039a 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java @@ -18,6 +18,8 @@ import com.cloud.domain.Domain; import com.cloud.serializer.Param; + +import java.util.Objects; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -75,7 +77,7 @@ public OauthProviderResponse(String id, String provider, String description, Str this.clientId = clientId; this.secretKey = secretKey; this.redirectUri = redirectUri; - if (domain != null) { + if (Objects.nonNull(domain)) { this.domainUuid = domain.getUuid(); this.domainName = domain.getName(); } diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java index 0818152f0208..606841a30fe7 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.oauth2.vo.OauthProviderVO; import java.util.List; +import java.util.Objects; public class OauthProviderDaoImpl extends GenericDaoBase implements OauthProviderDao { @@ -56,10 +57,10 @@ public List listByDomainIncludingGlobal(Long domainId) { @Override public OauthProviderVO findByProviderAndDomainWithGlobalFallback(String provider, Long domainId) { OauthProviderVO providerVO = null; - if (domainId != null) { + if (Objects.nonNull(domainId)) { providerVO = findByProviderAndDomain(provider, domainId); } - if (providerVO == null) { + if (Objects.isNull(providerVO)) { providerVO = findByProviderAndDomain(provider, null); } return providerVO; From 1a23f3a9cd5212702ffc1e49bbd7863051b4ba5b Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 19:31:52 -0500 Subject: [PATCH 19/24] Update OAuth2UserAuthenticatorTest to include domainId in user verification method --- .../cloudstack/oauth2/OAuth2UserAuthenticatorTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java index 5cb7d1a7e226..5252d929d40d 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java @@ -93,7 +93,7 @@ public void testAuthenticateWithValidCredentials() { when(userAccountDao.getUserAccount(username, domainId)).thenReturn(userAccount); when(userDao.getUser(userAccount.getId())).thenReturn(user); when(userOAuth2mgr.getUserOAuth2AuthenticationProvider(provider[0])).thenReturn(userOAuth2Authenticator); - when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0])).thenReturn(true); + when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0], domainId)).thenReturn(true); Map requestParameters = new HashMap<>(); requestParameters.put("provider", provider); @@ -108,7 +108,7 @@ public void testAuthenticateWithValidCredentials() { verify(userAccountDao).getUserAccount(username, domainId); verify(userDao).getUser(userAccount.getId()); verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]); - verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]); + verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0], domainId); } @Test @@ -126,7 +126,7 @@ public void testAuthenticateWithInvalidCredentials() { when(userAccountDao.getUserAccount(username, domainId)).thenReturn(userAccount); when(userDao.getUser(userAccount.getId())).thenReturn(user); when(userOAuth2mgr.getUserOAuth2AuthenticationProvider(provider[0])).thenReturn(userOAuth2Authenticator); - when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0])).thenReturn(false); + when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0], domainId)).thenReturn(false); Map requestParameters = new HashMap<>(); requestParameters.put("provider", provider); @@ -141,7 +141,7 @@ public void testAuthenticateWithInvalidCredentials() { verify(userAccountDao).getUserAccount(username, domainId); verify(userDao).getUser(userAccount.getId()); verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]); - verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]); + verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0], domainId); } @Test From 6d73964178077887a68dbf89d455a1cb07ba7f1a Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 19:46:28 -0500 Subject: [PATCH 20/24] Remove unnecessary blank line and unused imports in OAuth provider command classes --- .../cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java | 1 - .../oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java index 07412c17dbb2..d4506c0bac2b 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java @@ -65,7 +65,6 @@ public final class UpdateOAuthProviderCmd extends BaseCmd { @Inject OAuth2AuthManager _oauthMgr; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java index de51d22a0c65..f884016656ee 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java @@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; + import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -47,9 +48,6 @@ import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang3.StringUtils; -import com.cloud.api.response.ApiResponseSerializer; -import com.cloud.user.Account; - @APICommand(name = "verifyOAuthCodeAndGetUser", description = "Verify the OAuth Code and fetch the corresponding user from provider", responseObject = OauthProviderResponse.class, entityType = {}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0", From a428d1a26bed488c4c7f963920384f8b8202da3e Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:13:32 -0500 Subject: [PATCH 21/24] Refactor and cleanup --- .../oauth2/github/GithubOAuth2Provider.java | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java index 824820e84dfe..f66f22f52794 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java @@ -63,54 +63,6 @@ public String verifyCodeAndFetchEmail(String secretCode) { return verifyCodeAndFetchEmail(secretCode, null); } - public String getUserEmailAddress() throws CloudRuntimeException { - if (StringUtils.isEmpty(accessToken)) { - throw new CloudRuntimeException("Access Token not found to fetch the email address"); - } - - String apiUrl = "https://api.github.com/user/emails"; - String email = null; - try { - URL url = new URL(apiUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Authorization", "token " + accessToken); - - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String inputLine; - StringBuilder response = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); - } - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode jsonNode = objectMapper.readTree(response.toString()); - if (jsonNode != null && jsonNode.isArray()) { - JsonNode firstObject = jsonNode.get(0); - email = firstObject.get("email").asText(); - } else { - throw new CloudRuntimeException("Invalid JSON format found while accessing email from github"); - } - } catch (Exception e) { - throw new CloudRuntimeException(String.format("Error occurred while accessing email from github: %s", e.getMessage())); - } } - } else { - throw new CloudRuntimeException(String.format("HTTP Request Failed with error code: %s", responseCode)); - } - } catch (IOException e) { - throw new CloudRuntimeException(String.format("Error while trying to fetch email from github : %s", e.getMessage())); - } - - return email; - } - - private void clearAccessToken() { - accessToken = null; - } - @Override public boolean verifyUser(String email, String secretCode, Long domainId) { if (StringUtils.isAnyEmpty(email, secretCode)) { @@ -190,4 +142,52 @@ protected String getAccessToken(String secretCode, Long domainId) throws CloudRu return accessToken; } + public String getUserEmailAddress() throws CloudRuntimeException { + if (StringUtils.isEmpty(accessToken)) { + throw new CloudRuntimeException("Access Token not found to fetch the email address"); + } + + String apiUrl = "https://api.github.com/user/emails"; + String email = null; + try { + URL url = new URL(apiUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Authorization", "token " + accessToken); + + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(response.toString()); + if (jsonNode != null && jsonNode.isArray()) { + JsonNode firstObject = jsonNode.get(0); + email = firstObject.get("email").asText(); + } else { + throw new CloudRuntimeException("Invalid JSON format found while accessing email from github"); + } + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Error occurred while accessing email from github: %s", e.getMessage())); + } } + } else { + throw new CloudRuntimeException(String.format("HTTP Request Failed with error code: %s", responseCode)); + } + } catch (IOException e) { + throw new CloudRuntimeException(String.format("Error while trying to fetch email from github : %s", e.getMessage())); + } + + return email; + } + + private void clearAccessToken() { + accessToken = null; + } + } From b3674345924bcb1cdbc587f0a85058aa3b7d0342 Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:14:38 -0500 Subject: [PATCH 22/24] Remove unnecessary blank lines --- .../apache/cloudstack/oauth2/github/GithubOAuth2Provider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java index f66f22f52794..48d55d6820e1 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java @@ -189,5 +189,4 @@ public String getUserEmailAddress() throws CloudRuntimeException { private void clearAccessToken() { accessToken = null; } - } From 42a8651948374d58ab3ad2690f2628b61dd624ae Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:42:19 -0500 Subject: [PATCH 23/24] Enhance RegisterOAuthProviderCmdTest with additional provider mock data --- .../api/command/RegisterOAuthProviderCmdTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java index 4331d4b283db..7e1a96e0c1ea 100644 --- a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java +++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.oauth2.api.command; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -46,7 +47,14 @@ public void setUp() throws Exception { @Test public void testExecute() throws ServerApiException { OauthProviderVO provider = mock(OauthProviderVO.class); - when(_oauth2mgr.registerOauthProvider(_cmd)).thenReturn(provider); + when(provider.getDomainId()).thenReturn(null); + when(provider.getUuid()).thenReturn("test-uuid"); + when(provider.getProvider()).thenReturn("github"); + when(provider.getDescription()).thenReturn("test"); + when(provider.getClientId()).thenReturn("client-id"); + when(provider.getSecretKey()).thenReturn("secret-key"); + when(provider.getRedirectUri()).thenReturn("http://localhost"); + when(_oauth2mgr.registerOauthProvider(any(RegisterOAuthProviderCmd.class))).thenReturn(provider); _cmd.execute(); assertEquals(ApiConstants.OAUTH_PROVIDER, ((OauthProviderResponse)_cmd.getResponseObject()).getObjectName()); From 7a55ecb48014b6593996f73beb5281f72da68d9e Mon Sep 17 00:00:00 2001 From: Damans227 <61474540+Damans227@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:34:32 -0500 Subject: [PATCH 24/24] Remove startup gate from OAuth plugin initialization to support dynamic config toggling --- .../apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java index 89474cb21073..53270b470915 100644 --- a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java +++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java @@ -64,12 +64,8 @@ public List> getAuthCommands() { @Override public boolean start() { - if (isOAuthPluginEnabled(null)) { - logger.info("OAUTH plugin loaded"); - initializeUserOAuth2AuthenticationProvidersMap(); - } else { - logger.info("OAUTH plugin not enabled so not loading"); - } + initializeUserOAuth2AuthenticationProvidersMap(); + logger.info("OAUTH plugin loaded"); return true; }