From 2ad135afb02e0ba236c68275a159d593bcd39eb6 Mon Sep 17 00:00:00 2001 From: Daman Arora Date: Wed, 12 Aug 2026 12:20:12 -0400 Subject: [PATCH] oauth2: enforce oauth2.plugins as an allow-list for OAuth2 providers --- .../oauth2/OAuth2AuthManagerImpl.java | 27 ++++++++++- .../oauth2/OAuth2AuthManagerImplTest.java | 46 +++++++++++++++++++ 2 files changed, 71 insertions(+), 2 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 c3bad43be40e..18d40e164397 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 @@ -120,7 +120,16 @@ public List> getCommands() { @Override public List listUserOAuth2AuthenticationProviders() { - return userOAuth2AuthenticationProviders; + if (userOAuth2AuthenticationProviders == null) { + return userOAuth2AuthenticationProviders; + } + List allowed = new ArrayList<>(); + for (UserOAuth2Authenticator provider : userOAuth2AuthenticationProviders) { + if (isProviderAllowed(provider.getName())) { + allowed.add(provider); + } + } + return allowed; } @Override @@ -128,12 +137,26 @@ public UserOAuth2Authenticator getUserOAuth2AuthenticationProvider(String provid if (StringUtils.isEmpty(providerName)) { throw new CloudRuntimeException("OAuth2 authentication provider name is empty"); } - if (!userOAuth2AuthenticationProvidersMap.containsKey(providerName.toLowerCase())) { + if (!userOAuth2AuthenticationProvidersMap.containsKey(providerName.toLowerCase()) || !isProviderAllowed(providerName)) { throw new CloudRuntimeException(String.format("Failed to find OAuth2 authentication provider by the name: %s.", providerName)); } return userOAuth2AuthenticationProvidersMap.get(providerName.toLowerCase()); } + // oauth2.plugins is an allow-list: a provider must be named in it to be usable at all. + protected boolean isProviderAllowed(String providerName) { + String allowedPlugins = OAuth2AuthManager.OAuth2Plugins.value(); + if (StringUtils.isEmpty(allowedPlugins)) { + return true; + } + for (String allowed : allowedPlugins.trim().split("\\s*,\\s*")) { + if (allowed.equalsIgnoreCase(providerName)) { + return true; + } + } + return false; + } + public List getUserOAuth2AuthenticationProviders() { return userOAuth2AuthenticationProviders; } 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 e3e8f7594b37..0613c03e4354 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 @@ -24,6 +24,7 @@ import com.cloud.user.DomainService; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.auth.UserOAuth2Authenticator; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.MessageSubscriber; import org.apache.cloudstack.oauth2.api.command.DeleteOAuthProviderCmd; @@ -49,6 +50,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doNothing; @@ -525,6 +527,50 @@ public void testGetUserOAuth2AuthenticationProviderNotFound() { } } + @Test + public void testGetUserOAuth2AuthenticationProviderRejectsProviderNotInPluginsList() { + UserOAuth2Authenticator googleAuthenticator = Mockito.mock(UserOAuth2Authenticator.class); + OAuth2AuthManagerImpl.userOAuth2AuthenticationProvidersMap.put("google", googleAuthenticator); + try { + Mockito.doReturn(false).when(_authManager).isProviderAllowed("google"); + + try { + _authManager.getUserOAuth2AuthenticationProvider("google"); + Assert.fail("Expected CloudRuntimeException was not thrown"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("google")); + } + } finally { + OAuth2AuthManagerImpl.userOAuth2AuthenticationProvidersMap.remove("google"); + } + } + + @Test + public void testListUserOAuth2AuthenticationProvidersFiltersOutDisallowedPlugins() { + UserOAuth2Authenticator googleAuthenticator = Mockito.mock(UserOAuth2Authenticator.class); + when(googleAuthenticator.getName()).thenReturn("google"); + UserOAuth2Authenticator keycloakAuthenticator = Mockito.mock(UserOAuth2Authenticator.class); + when(keycloakAuthenticator.getName()).thenReturn("keycloak"); + _authManager.setUserOAuth2AuthenticationProviders(Arrays.asList(googleAuthenticator, keycloakAuthenticator)); + + Mockito.doReturn(true).when(_authManager).isProviderAllowed("google"); + Mockito.doReturn(false).when(_authManager).isProviderAllowed("keycloak"); + + List result = _authManager.listUserOAuth2AuthenticationProviders(); + + assertEquals(1, result.size()); + assertEquals("google", result.get(0).getName()); + } + + @Test + public void testIsProviderAllowedMatchesConfiguredPluginsListCaseInsensitively() { + // no ConfigDepot is wired in this unit test, so OAuth2Plugins.value() falls back to + // its default of "google,github" + assertTrue(_authManager.isProviderAllowed("google")); + assertTrue(_authManager.isProviderAllowed("Google")); + assertFalse(_authManager.isProviderAllowed("keycloak")); + } + // Multiple-domain OAuth tests @Test