You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by we...@apache.org on 2026/08/19 10:28:23 UTC
(cloudstack) 01/02: Merge remote-tracking branch 'origin/4.22'
This is an automated email from the ASF dual-hosted git repository.
weizhouapache pushed a commit to branch 4.23.0-merge-4.22.1.1
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 2d2a4c7d3484b6cad168eb837a42d10f264ff8de
Merge: 64178ead02d 85bcdb1cc48
Author: Wei Zhou <we...@apache.org>
AuthorDate: Wed Aug 19 11:31:38 2026 +0200
Merge remote-tracking branch 'origin/4.22'
.../java/org/apache/cloudstack/acl/APIChecker.java | 28 +++
.../org/apache/cloudstack/api/ApiArgValidator.java | 73 +++++-
.../admin/vm/ResetVMPasswordCmdByAdmin.java | 2 +-
.../autoscale/CreateAutoScaleVmProfileCmd.java | 2 +
.../autoscale/UpdateAutoScaleVmProfileCmd.java | 1 +
.../backup/repository/AddBackupRepositoryCmd.java | 4 +-
.../repository/UpdateBackupRepositoryCmd.java | 8 +-
.../command/user/userdata/DeleteUserDataCmd.java | 17 +-
.../user/userdata/LinkUserDataToTemplateCmd.java | 3 +
.../api/command/user/vm/BaseDeployVMCmd.java | 1 +
.../api/command/user/vm/ResetVMPasswordCmd.java | 2 +-
.../api/command/user/vm/ResetVMUserDataCmd.java | 1 +
.../api/command/user/vm/UpdateVMCmd.java | 1 +
.../cloudstack/api/response/DomainResponse.java | 2 +-
.../cloudstack/api/response/HostTagResponse.java | 6 +-
.../direct/download/DirectDownloadManager.java | 10 +
.../user/userdata/DeleteUserDataCmdTest.java | 31 ++-
.../template/MetalinkTemplateDownloader.java | 7 +
.../directdownload/DirectDownloadCommand.java | 11 +-
.../direct/download/DirectDownloadHelper.java | 5 +-
.../download/MetalinkDirectTemplateDownloader.java | 55 ++++-
.../download/NfsDirectTemplateDownloader.java | 79 ++++++-
.../download/NfsDirectTemplateDownloaderTest.java | 73 ++++++
.../com/cloud/vm/VirtualMachineManagerImpl.java | 6 +-
.../java/com/cloud/host/dao/HostTagsDaoImpl.java | 1 -
.../framework/config/impl/ConfigDepotImpl.java | 11 +-
.../framework/jobs/AsyncJobDataHelper.java | 150 ++++++++++++
.../framework/jobs/impl/AsyncJobManagerImpl.java | 50 ++--
.../cloudstack/framework/jobs/impl/AsyncJobVO.java | 22 +-
.../framework/jobs/AsyncJobDataHelperTest.java | 249 ++++++++++++++++++++
.../framework/jobs/AsyncJobManagerTest.java | 2 -
.../acl/DynamicRoleBasedAPIAccessChecker.java | 34 +++
.../acl/DynamicRoleBasedAPIAccessCheckerTest.java | 132 +++++++++++
.../acl/ProjectRoleBasedApiAccessChecker.java | 5 +
.../acl/StaticRoleBasedAPIAccessChecker.java | 16 ++
.../mom/webhook/WebhookApiServiceImpl.java | 81 ++++---
.../mom/webhook/WebhookDeliveryThread.java | 116 ++++++++--
.../cloudstack/mom/webhook/WebhookService.java | 38 ++-
.../cloudstack/mom/webhook/WebhookServiceImpl.java | 61 ++++-
.../mom/webhook/WebhookUrlValidator.java | 123 ++++++++++
.../mom/webhook/dao/WebhookDeliveryDao.java | 3 +-
.../mom/webhook/dao/WebhookDeliveryDaoImpl.java | 13 +-
.../mom/webhook/dao/WebhookDeliveryJoinDao.java | 6 +-
.../webhook/dao/WebhookDeliveryJoinDaoImpl.java | 9 +-
.../mom/webhook/WebhookApiServiceImplTest.java | 257 +++++++++++++++++----
.../mom/webhook/WebhookDeliveryThreadTest.java | 7 +
.../mom/webhook/WebhookUrlValidatorTest.java | 100 ++++++++
.../LibvirtRestoreBackupCommandWrapper.java | 135 ++++++-----
.../LibvirtRestoreBackupCommandWrapperTest.java | 120 +++++-----
.../cluster/KubernetesClusterManagerImpl.java | 8 +-
.../contrail/management/MockAccountManager.java | 4 +
.../api/response/LdapConfigurationResponse.java | 17 +-
.../apache/cloudstack/ldap/LdapManagerImpl.java | 25 +-
.../oauth2/api/command/ListOAuthProvidersCmd.java | 43 +++-
.../oauth2/google/GoogleOAuth2Provider.java | 94 +++++---
.../oauth2/google/GoogleOAuth2ProviderTest.java | 176 ++++++++------
.../api/command/SAML2LoginAPIAuthenticatorCmd.java | 78 ++++---
scripts/vm/hypervisor/kvm/nasbackup.sh | 41 +++-
server/src/main/java/com/cloud/api/ApiServer.java | 4 +-
.../com/cloud/api/dispatch/ParamProcessWorker.java | 6 +
.../java/com/cloud/api/query/QueryManagerImpl.java | 63 ++++-
.../com/cloud/api/query/vo/AsyncJobJoinVO.java | 3 +-
.../com/cloud/dc/dao/DedicatedResourceDao.java | 6 +
.../com/cloud/dc/dao/DedicatedResourceDaoImpl.java | 12 +
.../com/cloud/resource/ResourceManagerImpl.java | 75 +++++-
.../main/java/com/cloud/server/StatsCollector.java | 14 +-
.../java/com/cloud/storage/StorageManagerImpl.java | 8 +-
.../com/cloud/storage/VolumeApiServiceImpl.java | 16 +-
.../com/cloud/template/TemplateManagerImpl.java | 25 +-
.../main/java/com/cloud/user/AccountManager.java | 3 +
.../java/com/cloud/user/AccountManagerImpl.java | 82 ++++---
.../main/java/com/cloud/vm/UserVmManagerImpl.java | 10 +-
.../cloud/vm/snapshot/VMSnapshotManagerImpl.java | 10 +-
.../cloudstack/acl/ProjectRoleManagerImpl.java | 17 +-
.../org/apache/cloudstack/acl/RoleManagerImpl.java | 3 +
.../annotation/AnnotationManagerImpl.java | 9 +-
.../diagnostics/DiagnosticsServiceImpl.java | 37 ++-
.../direct/download/DirectDownloadManagerImpl.java | 27 ++-
.../cloud/api/dispatch/ParamProcessWorkerTest.java | 20 ++
.../com/cloud/api/query/QueryManagerImplTest.java | 230 +++++++++++++++++-
.../cloud/resource/ResourceManagerImplTest.java | 90 ++++++++
.../java/com/cloud/server/StatsCollectorTest.java | 43 ++++
.../cloud/template/TemplateManagerImplTest.java | 64 +++++
.../com/cloud/user/AccountManagerImplTest.java | 152 +++++++++++-
.../java/com/cloud/vm/UserVmManagerImplTest.java | 1 -
.../cloudstack/acl/ProjectRoleManagerImplTest.java | 245 ++++++++++++++++++++
.../diagnostics/DiagnosticsServiceImplTest.java | 133 +++++++++++
.../consoleproxy/ConsoleProxyNoVNCServer.java | 10 +-
systemvm/debian/opt/cloud/bin/cs/CsDhcp.py | 67 +++++-
test/integration/smoke/test_webhook_delivery.py | 67 +++++-
test/integration/smoke/test_webhook_lifecycle.py | 83 ++++++-
ui/package.json | 2 +-
ui/src/config/section/compute.js | 3 +-
ui/src/config/section/infra/routers.js | 3 +-
ui/src/config/section/infra/systemVms.js | 3 +-
ui/src/config/section/network.js | 3 +-
ui/src/config/section/user.js | 3 +-
ui/src/utils/util.js | 9 +
.../src/main/java/com/cloud/utils/StringUtils.java | 23 ++
utils/src/main/java/com/cloud/utils/UriUtils.java | 145 +++++++++++-
100 files changed, 3895 insertions(+), 589 deletions(-)
diff --cc api/src/main/java/org/apache/cloudstack/acl/APIChecker.java
index 286a3598e4f,c9a8d384945..6defad5523c
--- a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java
+++ b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java
@@@ -20,10 -21,13 +21,14 @@@ import com.cloud.exception.RequestLimit
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.component.Adapter;
+import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
+ import java.util.ArrayList;
import java.util.List;
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
/**
* APICheckers is designed to verify the ownership of resources and to control the access to APIs.
*/
@@@ -43,6 -48,27 +49,28 @@@ public interface APIChecker extends Ada
* @return the list of allowed apis for the given user
*/
List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException;
+
+ default List<String> getApisAllowedToAccount(Account account, List<String> apiNames) {
+ List<String> allowedApis = new ArrayList<>();
+ for (String apiName : apiNames) {
+ try {
+ checkAccess(account, apiName);
+ allowedApis.add(apiName);
+ } catch (RequestLimitException e) {
+ // Non-ACL failure (e.g. rate limiting) should not be treated as simple "not allowed".
+ // Propagate as unchecked so callers are aware of the failure.
+ throw new RuntimeException("Failed to check access for API [" + apiName + "] due to request limits", e);
+ } catch (PermissionDeniedException e) {
+ LOGGER.trace("Account [" + account + "] is not allowed to access API [" + apiName + "]");
+ }
+ }
+ return allowedApis;
+ }
+
boolean isEnabled();
+ List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType);
+
+ default void refreshRoleCacheOnPermissionsChange(Role role) {
+ // Only applicable for dynamic role based checkers
+ }
}
diff --cc api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
index 38e9921853a,50129f26d70..8d79ac4af45
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
@@@ -157,10 -147,11 +157,11 @@@ public abstract class BaseDeployVMCmd e
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
"You also need to change vm.userdata.max.length value",
length = 1048576)
- private String userData;
+ protected String userData;
+ @ACL
@Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID, entityType = UserDataResponse.class, description = "the ID of the Userdata", since = "4.18")
- private Long userdataId;
+ protected Long userdataId;
@Parameter(name = ApiConstants.USER_DATA_DETAILS, type = CommandType.MAP, description = "used to specify the parameters values for the variables in userdata.", since = "4.18")
private Map userdataDetails;
diff --cc plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java
index 71fbbb4a365,c89000e7ed1..84fd5d15446
--- a/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java
+++ b/plugins/acl/dynamic-role-based/src/test/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessCheckerTest.java
@@@ -198,67 -198,133 +200,197 @@@ public class DynamicRoleBasedAPIAccessC
Assert.assertEquals(0, apisReceived.size());
}
+ @Test(expected = UnavailableCommandException.class)
+ public void checkAccessTestInvalidApiKeyPairPermission() {
+ final String api = "someDeniedApi";
+ final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null);
+ assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
+ }
+
+ @Test(expected = UnavailableCommandException.class)
+ public void checkAccessTestUnrelatedApiKeyPairPermission() {
+ final String api = "someDeniedApi";
+ final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, "apiName", Permission.ALLOW, null);
+ assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
+ }
+
+ @Test
+ public void checkAccessTestValidApiKeyPairPermission() {
+ final String api = "someAllowedApi";
+ final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null);
+ assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
+ }
+
+ @Test
+ public void checkAccessTestValidMultipleApiKeyPermissions() {
+ final String api = "someAllowedApi";
+ final ApiKeyPairPermission[] permissions = new ApiKeyPairPermission[]{
+ new ApiKeyPairPermissionVO(1L, "someDeniedApi", Permission.DENY, null),
+ new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null)
+ };
+ assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
+ }
+
+ @Test(expected = UnavailableCommandException.class)
+ public void checkAccessTestInvalidMultipleApiKeyPermissions() {
+ final String api = "someDeniedApi";
+ final ApiKeyPairPermission[] permissions = new ApiKeyPairPermission[]{
+ new ApiKeyPairPermissionVO(1L, "someAllowedApi", Permission.ALLOW, null),
+ new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null)
+ };
+ assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
+ }
+
+
+ @Test
+ public void checkAccessTestValidApiKeyPairPermissionWithNullOverride() {
+ final String api = "someAllowedApi";
+ final ApiKeyPairPermission[] emptyPermissionArray = List.of().toArray(new ApiKeyPairPermission[0]);
+ final RolePermission permission = new RolePermissionVO(1L, api, Permission.ALLOW, null);
+ Mockito.doReturn(Collections.singletonList(permission)).when(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
+
+ assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, emptyPermissionArray));
+ Mockito.verify(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
+ }
+
+ @Test(expected = UnavailableCommandException.class)
+ public void checkAccessTestInvalidApiKeyPairPermissionWithNullOverride() {
+ final String api = "someDeniedApi";
+ final ApiKeyPairPermission[] emptyPermissionArray = List.of().toArray(new ApiKeyPairPermission[0]);
+ final RolePermission permission = new RolePermissionVO(1L, api, Permission.DENY, null);
+ Mockito.doReturn(Collections.singletonList(permission)).when(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
+
+ assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, emptyPermissionArray));
+ Mockito.verify(roleServiceMock, Mockito.times(1)).findAllPermissionsBy(Mockito.anyLong());
+ }
++
+ // --- Tests for checkAccess(Account, String) ---
+
+ @Test(expected = PermissionDeniedException.class)
+ public void testCheckAccessAccountNullRoleShouldThrow() {
+ Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(null);
+ apiAccessCheckerSpy.checkAccess(getTestAccount(), "someApi");
+ }
+
+ @Test
+ public void testCheckAccessAccountAdminShouldAllow() {
+ Account adminAccount = new AccountVO("root admin", 1L, null, Account.Type.ADMIN, "admin-uuid");
+ Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(new RoleVO(1L, "Admin", RoleType.Admin, "default admin role"));
+ assertTrue(apiAccessCheckerSpy.checkAccess(adminAccount, "anyApi"));
+ }
+
+ @Test
+ public void testCheckAccessAccountAllowedApi() {
+ final String allowedApiName = "someAllowedApi";
+ final RolePermission permission = new RolePermissionVO(1L, allowedApiName, Permission.ALLOW, null);
+ Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+ assertTrue(apiAccessCheckerSpy.checkAccess(getTestAccount(), allowedApiName));
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void testCheckAccessAccountDeniedApi() {
+ final String deniedApiName = "someDeniedApi";
+ final RolePermission permission = new RolePermissionVO(1L, deniedApiName, Permission.DENY, null);
+ Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+ apiAccessCheckerSpy.checkAccess(getTestAccount(), deniedApiName);
+ }
+
+ @Test
+ public void testCheckAccessAccountUsesCachedPermissions() throws Exception {
+ // Enable caching by setting a positive cachePeriod
+ Field cachePeriodField = DynamicRoleBasedAPIAccessChecker.class.getDeclaredField("cachePeriod");
+ cachePeriodField.setAccessible(true);
+ cachePeriodField.set(apiAccessCheckerSpy, 1);
+
+ Field rpCacheField = DynamicRoleBasedAPIAccessChecker.class.getDeclaredField("rolePermissionsCache");
+ rpCacheField.setAccessible(true);
+ rpCacheField.set(apiAccessCheckerSpy, new LazyCache<Long, Pair<Role, List<RolePermission>>>(32, 1, apiAccessCheckerSpy::getRolePermissions));
+
+ final String allowedApiName = "someAllowedApi";
+ final RolePermission permission = new RolePermissionVO(1L, allowedApiName, Permission.ALLOW, null);
+ Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+
+ // First call should populate the cache
+ apiAccessCheckerSpy.checkAccess(getTestAccount(), allowedApiName);
+ // Second call should use cached permissions and not hit the DAO again
+ apiAccessCheckerSpy.checkAccess(getTestAccount(), allowedApiName);
+
+ Mockito.verify(roleServiceMock, Mockito.times(1)).findAllPermissionsBy(Mockito.anyLong());
+ }
+
+ // --- Tests for getApisAllowedToAccount ---
+
+ @Test
+ public void testGetApisAllowedToAccountDisabledShouldReturnAll() {
+ Mockito.doReturn(false).when(apiAccessCheckerSpy).isEnabled();
+ List<String> input = new ArrayList<>(Arrays.asList("api1", "api2", "api3"));
+ List<String> result = apiAccessCheckerSpy.getApisAllowedToAccount(getTestAccount(), input);
+ Assert.assertEquals(3, result.size());
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void testGetApisAllowedToAccountNullRoleShouldThrow() {
+ Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(null);
+ apiAccessCheckerSpy.getApisAllowedToAccount(getTestAccount(), new ArrayList<>(Arrays.asList("api1")));
+ }
+
+ @Test
+ public void testGetApisAllowedToAccountAdminShouldReturnAll() {
+ Account adminAccount = new AccountVO("root admin", 1L, null, Account.Type.ADMIN, "admin-uuid");
+ Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(new RoleVO(1L, "Admin", RoleType.Admin, "default admin role"));
+ List<String> input = new ArrayList<>(Arrays.asList("api1", "api2", "api3"));
+ List<String> result = apiAccessCheckerSpy.getApisAllowedToAccount(adminAccount, input);
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals(input, result);
+ }
+
+ @Test
+ public void testGetApisAllowedToAccountFiltersCorrectly() {
+ final RolePermission allowPermission = new RolePermissionVO(1L, "allowedApi", Permission.ALLOW, null);
+ final RolePermission denyPermission = new RolePermissionVO(1L, "deniedApi", Permission.DENY, null);
+ Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Arrays.asList(allowPermission, denyPermission));
+ List<String> input = new ArrayList<>(Arrays.asList("allowedApi", "deniedApi", "unknownApi"));
+ List<String> result = apiAccessCheckerSpy.getApisAllowedToAccount(getTestAccount(), input);
+ Assert.assertEquals(1, result.size());
+ Assert.assertEquals("allowedApi", result.get(0));
+ }
+
+ @Test
+ public void testGetApisAllowedToAccountAnnotationFallback() {
+ Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.emptyList());
+ apiAccessCheckerSpy.addApiToRoleBasedAnnotationsMap(RoleType.User, "annotatedApi");
+ List<String> input = new ArrayList<>(Arrays.asList("annotatedApi", "unknownApi"));
+ List<String> result = apiAccessCheckerSpy.getApisAllowedToAccount(getTestAccount(), input);
+ Assert.assertEquals(1, result.size());
+ Assert.assertEquals("annotatedApi", result.get(0));
+ }
+
+ @Test
+ public void testGetApisAllowedToAccountUsesCachedPermissions() {
+ try {
+ // Ensure caching is enabled by setting a positive cachePeriod
+ Field cachePeriodField = DynamicRoleBasedAPIAccessChecker.class.getDeclaredField("cachePeriod");
+ cachePeriodField.setAccessible(true);
+ cachePeriodField.set(apiAccessCheckerSpy, 1);
+
+ Field rpCacheField = DynamicRoleBasedAPIAccessChecker.class.getDeclaredField("rolePermissionsCache");
+ rpCacheField.setAccessible(true);
+ rpCacheField.set(apiAccessCheckerSpy, new LazyCache<Long, Pair<Role, List<RolePermission>>>(32, 1, apiAccessCheckerSpy::getRolePermissions));
+
+ final RolePermission permission = new RolePermissionVO(1L, "api1", Permission.ALLOW, null);
+ Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
+
+ Account account = getTestAccount();
+ List<String> apis = new ArrayList<>(Arrays.asList("api1"));
+
+ // First call should load permissions from the DAO and populate the cache
+ apiAccessCheckerSpy.getApisAllowedToAccount(account, apis);
+ // Second call should use cached permissions and not hit the DAO again
+ apiAccessCheckerSpy.getApisAllowedToAccount(account, apis);
+
+ Mockito.verify(roleServiceMock, Mockito.times(1)).findAllPermissionsBy(Mockito.anyLong());
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ Assert.fail("Failed to set cachePeriod for test: " + e.getMessage());
+ }
+ }
}
diff --cc plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImpl.java
index a484f29e8d2,37f4b3d0a92..9c2ffefdaf4
--- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImpl.java
+++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImpl.java
@@@ -66,10 -59,8 +66,9 @@@ import com.cloud.exception.PermissionDe
import com.cloud.projects.Project;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
+import com.cloud.utils.EnumUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
- import com.cloud.utils.UriUtils;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder;
@@@ -235,25 -224,13 +234,32 @@@ public class WebhookApiServiceImpl exte
throw new InvalidParameterValueException(error);
}
+ protected WebhookFilterResponse createWebhookFilterResponse(WebhookFilter webhookFilter, WebhookVO webhookVO) {
+ WebhookFilterResponse response = new WebhookFilterResponse();
+ response.setObjectName("webhookfilter");
+ response.setId(webhookFilter.getUuid());
+ if (webhookVO == null) {
+ webhookVO = webhookDao.findById(webhookFilter.getWebhookId());
+ }
+ if (webhookVO != null) {
+ response.setWebhookId(webhookVO.getUuid());
+ response.setWebhookName(webhookVO.getName());
+ }
+ response.setType(webhookFilter.getType().toString());
+ response.setMode(webhookFilter.getMode().toString());
+ response.setMatchType(webhookFilter.getMatchType().toString());
+ response.setValue(webhookFilter.getValue());
+ response.setCreated(webhookFilter.getCreated());
+ return response;
+ }
+
+ protected URI validatePayloadUrlByDeliverySecurityPolicy(String payloadUrl, long domainId) {
+ return WebhookUrlValidator.validateWebhookDestinationUrl(payloadUrl,
+ WebhookService.WebhookDeliveryAllowHttp.valueIn(domainId),
+ WebhookService.WebhookDeliveryBlocklist.valueIn(domainId),
+ WebhookService.WebhookDeliveryBlockLocalAddresses.value());
+ }
+
@Override
public ListResponse<WebhookResponse> listWebhooks(ListWebhooksCmd cmd) {
final CallContext ctx = CallContext.current();
@@@ -263,25 -240,8 +269,27 @@@
final String name = cmd.getName();
final String keyword = cmd.getKeyword();
final String scopeStr = cmd.getScope();
+ final boolean isCallerRootAdmin = accountManager.isRootAdmin(caller.getId());
+ final boolean isCallerAdmin = isCallerRootAdmin || accountManager.isAdmin(caller.getId());
++
+ Webhook.Scope scope = null;
+ if (StringUtils.isNotEmpty(scopeStr)) {
+ scope = EnumUtils.getEnumIgnoreCase(Webhook.Scope.class, scopeStr);
+ if (scope == null) {
+ throw new InvalidParameterValueException("Invalid scope specified");
+ }
+ }
- if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(caller.getType())) ||
- (Webhook.Scope.Domain.equals(scope) &&
- !List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(caller.getType()))) {
++ if ((Webhook.Scope.Global.equals(scope) && !isCallerRootAdmin) ||
++ (Webhook.Scope.Domain.equals(scope) && !isCallerAdmin)) {
+ throw new InvalidParameterValueException(String.format("Scope %s can not be specified", scope));
+ }
+ Webhook.State state = null;
+ if (StringUtils.isNotEmpty(stateStr)) {
+ state = EnumUtils.getEnumIgnoreCase(Webhook.State.class, stateStr);
+ if (state == null) {
+ throw new InvalidParameterValueException("Invalid state specified");
+ }
+ }
List<WebhookResponse> responsesList = new ArrayList<>();
List<Long> permittedAccounts = new ArrayList<>();
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject =
@@@ -342,9 -322,12 +350,11 @@@
final String scopeStr = cmd.getScope();
final String stateStr = cmd.getState();
Webhook.Scope scope = Webhook.Scope.Local;
+ final boolean isOwnerRootAdmin = accountManager.isRootAdmin(owner.getId());
+ final boolean isOwnerAdmin = isOwnerRootAdmin || accountManager.isAdmin(owner.getId());
if (StringUtils.isNotEmpty(scopeStr)) {
- try {
- scope = Webhook.Scope.valueOf(scopeStr);
- } catch (IllegalArgumentException iae) {
+ scope = EnumUtils.getEnumIgnoreCase(Webhook.Scope.class, scopeStr);
+ if (scope == null) {
throw new InvalidParameterValueException("Invalid scope specified");
}
}
@@@ -424,27 -400,30 +431,28 @@@
updateNeeded = true;
}
if (StringUtils.isNotEmpty(stateStr)) {
- try {
- Webhook.State state = Webhook.State.valueOf(stateStr);
- webhook.setState(state);
- updateNeeded = true;
- } catch (IllegalArgumentException iae) {
+ Webhook.State state = EnumUtils.getEnumIgnoreCase(Webhook.State.class, stateStr);
+ if (state == null) {
throw new InvalidParameterValueException("Invalid state specified");
}
+ webhook.setState(state);
+ updateNeeded = true;
}
Account owner = accountManager.getAccount(webhook.getAccountId());
+ final boolean isOwnerRootAdmin = accountManager.isRootAdmin(owner.getId());
+ final boolean isOwnerAdmin = isOwnerRootAdmin || accountManager.isAdmin(owner.getId());
if (StringUtils.isNotEmpty(scopeStr)) {
- try {
- Webhook.Scope scope = Webhook.Scope.valueOf(scopeStr);
- if ((Webhook.Scope.Global.equals(scope) && !isOwnerRootAdmin) ||
- (Webhook.Scope.Domain.equals(scope) && !isOwnerAdmin)) {
- throw new InvalidParameterValueException(
- String.format("Scope %s can not be specified for owner %s", scope, owner.getName()));
- }
- webhook.setScope(scope);
- updateNeeded = true;
- } catch (IllegalArgumentException iae) {
+ Webhook.Scope scope = EnumUtils.getEnumIgnoreCase(Webhook.Scope.class, scopeStr);
+ if (scope == null) {
throw new InvalidParameterValueException("Invalid scope specified");
}
- if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(owner.getType())) ||
- (Webhook.Scope.Domain.equals(scope) &&
- !List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(owner.getType()))) {
++ if ((Webhook.Scope.Global.equals(scope) && !isOwnerRootAdmin) ||
++ (Webhook.Scope.Domain.equals(scope) && !isOwnerAdmin)) {
+ throw new InvalidParameterValueException(
+ String.format("Scope %s can not be specified for owner %s", scope, owner.getName()));
+ }
+ webhook.setScope(scope);
+ updateNeeded = true;
}
URI uri = URI.create(webhook.getPayloadUrl());
if (StringUtils.isNotEmpty(payloadUrl)) {
@@@ -576,17 -566,14 +599,17 @@@
webhook.setSecretKey(secretKey);
}
if (sslVerification != null) {
- webhook.setSslVerification(Boolean.TRUE.equals(sslVerification));
+ webhook.setSslVerification(sslVerification);
}
}
+ if (webhook != null) {
+ accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhook);
+ }
if (ObjectUtils.allNull(deliveryId, webhookId)) {
- webhook = new WebhookVO(owner.getDomainId(), owner.getId(), payloadUrl, secretKey,
+ webhook = new WebhookVO(caller.getDomainId(), caller.getId(), payloadUrl, secretKey,
Boolean.TRUE.equals(sslVerification));
}
- WebhookDelivery webhookDelivery = webhookService.executeWebhookDelivery(existingDelivery, webhook, payload);
+ WebhookDelivery webhookDelivery = webhookService.executeWebhookDelivery(existingDelivery, webhook, payload, uri);
if (webhookDelivery.getId() != WebhookDelivery.ID_DUMMY) {
return createWebhookDeliveryResponse(webhookDeliveryJoinDao.findById(webhookDelivery.getId()));
}
diff --cc plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookDeliveryThread.java
index 3f2d85458d3,e68fcc2f30c..9b67ba8eb03
--- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookDeliveryThread.java
+++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookDeliveryThread.java
@@@ -99,9 -104,7 +106,10 @@@ public class WebhookDeliveryThread impl
protected void setHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
if (webhook.isSslVerification()) {
- httpClient = HttpClients.custom().disableRedirectHandling().build();
+ httpClient = HttpClients.custom()
+ .setSSLContext(SSLContext.getDefault())
++ .disableRedirectHandling()
+ .build();
return;
}
httpClient = HttpClients
diff --cc plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookService.java
index 7d8f9a0a744,ed8defd27c0..446a87335a4
--- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookService.java
+++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookService.java
@@@ -57,9 -91,9 +91,11 @@@ public interface WebhookService extend
"Interval (in seconds) for cleaning up webhook deliveries",
false, ConfigKey.Scope.Global);
+
+
void handleEvent(Event event) throws EventBusException;
- WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload)
+ WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload, URI uri)
throws CloudRuntimeException;
+ void invalidateWebhooksCache();
+ void invalidateWebhookFiltersCache(long webhookId);
}
diff --cc plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookServiceImpl.java
index 624de54d41b,002916fef9d..85befa13a92
--- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookServiceImpl.java
+++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookServiceImpl.java
@@@ -89,10 -84,26 +90,29 @@@ public class WebhookServiceImpl extend
@Inject
AccountManager accountManager;
+ protected LazyCache<org.apache.commons.lang3.tuple.Pair<Long, List<Long>>, List<WebhookVO>> webhooksCache;
+ protected LazyCache<Long, List<WebhookFilterVO>> webhookFiltersCache;
+
- protected WebhookDeliveryThread getDeliveryJob(Event event, Webhook webhook, Pair<Integer, Integer> configs) {
+ static class DeliveryConfig {
+ final int tries;
+ final int timeout;
+ final String blocklist;
+ final boolean blockLocalAddresses;
+ final boolean allowRedirects;
+ final boolean allowHttp;
+
+ DeliveryConfig(int tries, int timeout, String blocklist, boolean blockLocalAddresses,
+ boolean allowRedirects, boolean allowHttp) {
+ this.tries = tries;
+ this.timeout = timeout;
+ this.blocklist = blocklist;
+ this.blockLocalAddresses = blockLocalAddresses;
+ this.allowRedirects = allowRedirects;
+ this.allowHttp = allowHttp;
+ }
+ }
+
+ protected WebhookDeliveryThread getDeliveryJob(Event event, Webhook webhook, DeliveryConfig config) {
WebhookDeliveryThread.WebhookDeliveryContext<WebhookDeliveryThread.WebhookDeliveryResult> context =
new WebhookDeliveryThread.WebhookDeliveryContext<>(null, event.getEventId(), webhook.getId());
AsyncCallbackDispatcher<WebhookServiceImpl, WebhookDeliveryThread.WebhookDeliveryResult> caller =
@@@ -182,28 -136,28 +206,33 @@@
domainIds.addAll(domainDao.getDomainParentIds(event.getResourceDomainId()));
}
List<WebhookVO> webhooks =
- webhookDao.listByEnabledForDelivery(event.getResourceAccountId(), domainIds);
+ webhooksCache.get(org.apache.commons.lang3.tuple.Pair.of(event.getResourceAccountId(), domainIds));
- Map<Long, Pair<Integer, Integer>> domainConfigs = new HashMap<>();
+ Map<Long, DeliveryConfig> domainConfigs = new HashMap<>();
for (WebhookVO webhook : webhooks) {
+ List<? extends WebhookFilter> filters = webhookFiltersCache.get(webhook.getId());
+ if (!isEventMatchingFilters(event, filters)) {
+ logger.debug("Skipping delivering {} to {} as it doesn't match filters", event, webhook);
+ continue;
+ }
if (!domainConfigs.containsKey(webhook.getDomainId())) {
domainConfigs.put(webhook.getDomainId(),
- new Pair<>(WebhookDeliveryTries.valueIn(webhook.getDomainId()),
- WebhookDeliveryTimeout.valueIn(webhook.getDomainId())));
+ new DeliveryConfig(
+ WebhookDeliveryTries.valueIn(webhook.getDomainId()),
+ WebhookDeliveryTimeout.valueIn(webhook.getDomainId()),
+ WebhookDeliveryBlocklist.valueIn(webhook.getDomainId()),
+ WebhookDeliveryBlockLocalAddresses.value(),
+ WebhookDeliveryAllowRedirects.valueIn(webhook.getDomainId()),
+ WebhookDeliveryAllowHttp.valueIn(webhook.getDomainId())));
}
- Pair<Integer, Integer> configs = domainConfigs.get(webhook.getDomainId());
- WebhookDeliveryThread job = getDeliveryJob(event, webhook, configs);
+ DeliveryConfig config = domainConfigs.get(webhook.getDomainId());
+ WebhookDeliveryThread job = getDeliveryJob(event, webhook, config);
jobs.add(job);
}
return jobs;
}
- protected Runnable getManualDeliveryJob(WebhookDelivery existingDelivery, Webhook webhook, String payload,
+ protected Runnable getManualDeliveryJob(WebhookDelivery existingDelivery, Webhook webhook, String payload, URI uri,
- AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
+ CompletableFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
if (StringUtils.isBlank(payload)) {
payload = "{ \"CloudStack\": \"works!\" }";
}
@@@ -358,10 -308,10 +395,10 @@@
}
@Override
- public WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload)
+ public WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload, URI uri)
throws CloudRuntimeException {
- AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future = new AsyncCallFuture<>();
+ CompletableFuture<WebhookDeliveryThread.WebhookDeliveryResult> future = new CompletableFuture<>();
- Runnable job = getManualDeliveryJob(delivery, webhook, payload, future);
+ Runnable job = getManualDeliveryJob(delivery, webhook, payload, uri, future);
webhookJobExecutor.submit(job);
WebhookDeliveryThread.WebhookDeliveryResult result = null;
WebhookDeliveryVO webhookDeliveryVO;
diff --cc plugins/event-bus/webhook/src/test/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImplTest.java
index eb0da8e75c6,e623b758cbb..0e536c0bb97
--- a/plugins/event-bus/webhook/src/test/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImplTest.java
+++ b/plugins/event-bus/webhook/src/test/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImplTest.java
@@@ -16,6 -16,6 +16,7 @@@
// under the License.
package org.apache.cloudstack.mom.webhook;
++import java.net.URI;
import java.util.Date;
import java.util.List;
@@@ -83,22 -60,13 +84,23 @@@ public class WebhookApiServiceImplTest
@Mock
WebhookDeliveryDao webhookDeliveryDao;
@Mock
+ WebhookDeliveryJoinDao webhookDeliveryJoinDao;
+ @Mock
+ WebhookFilterDao webhookFilterDao;
+ @Mock
+ ManagementServerHostDao managementServerHostDao;
+ @Mock
AccountManager accountManager;
+
@Mock
DomainDao domainDao;
+ @Mock
+ WebhookService webhookService;
- @Mock
- ManagementServerHostDao managementServerHostDao;
+ @Mock
+ Account caller;
+
+ @Spy
@InjectMocks
WebhookApiServiceImpl webhookApiServiceImpl = new WebhookApiServiceImpl();
@@@ -292,1026 -259,181 +293,1202 @@@
webhookApiServiceImpl.getNormalizedPayloadUrl("https://abc.com"));
}
+ @Test(expected = InvalidParameterValueException.class)
+ public void basicWebhookDeliveryApiCheckThrowsExceptionForInvalidDeliveryId() {
+ Mockito.when(webhookDeliveryDao.findById(1L)).thenReturn(null);
- webhookApiServiceImpl.basicWebhookDeliveryApiCheck(Mockito.mock(Account.class), 1L, null, null, null, null);
++ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(Mockito.mock(Account.class), 1L, null, null, null, null, false);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void basicWebhookDeliveryApiCheckThrowsExceptionForInvalidWebhookId() {
+ Mockito.when(webhookDao.findById(1L)).thenReturn(null);
- webhookApiServiceImpl.basicWebhookDeliveryApiCheck(Mockito.mock(Account.class), null, 1L, null, null, null);
++ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(Mockito.mock(Account.class), null, 1L, null, null, null, false);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void basicWebhookDeliveryApiCheckThrowsExceptionForEndDateBeforeStartDate() {
- webhookApiServiceImpl.basicWebhookDeliveryApiCheck(Mockito.mock(Account.class), null, null, null, new Date(), new Date(System.currentTimeMillis() - 1000));
++ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(Mockito.mock(Account.class), null, null, null, new Date(), new Date(System.currentTimeMillis() - 1000), false);
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void basicWebhookDeliveryApiCheckThrowsExceptionForNonAdminAccessToManagementServer() {
+ Account caller = Mockito.mock(Account.class);
- Mockito.when(caller.getType()).thenReturn(Account.Type.NORMAL);
- webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null);
++ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null, false);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void basicWebhookDeliveryApiCheckThrowsExceptionForInvalidManagementServerId() {
+ Account caller = Mockito.mock(Account.class);
- Mockito.when(caller.getType()).thenReturn(Account.Type.ADMIN);
+ Mockito.when(managementServerHostDao.findById(1L)).thenReturn(null);
- webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null);
++ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null, true);
+ }
+
+ @Test
+ public void basicWebhookDeliveryApiCheckReturnsManagementServerHostVOForValidInput() {
+ Account caller = Mockito.mock(Account.class);
- Mockito.when(caller.getType()).thenReturn(Account.Type.ADMIN);
+ ManagementServerHostVO managementServerHostVO = Mockito.mock(ManagementServerHostVO.class);
+ Mockito.when(managementServerHostDao.findById(1L)).thenReturn(managementServerHostVO);
- ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null);
++ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null, true);
++ }
++
+ @Test
+ public void shouldReturnNullWhenAllParametersAreNull() {
+ Account caller = Mockito.mock(Account.class);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, null, null, null, false);
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void shouldValidateDeliveryIdAndReturnNull() {
+ Account caller = Mockito.mock(Account.class);
+ Long deliveryId = 1L;
+ WebhookDeliveryVO deliveryVO = Mockito.mock(WebhookDeliveryVO.class);
+ Mockito.when(deliveryVO.getWebhookId()).thenReturn(1L);
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookDeliveryDao.findById(deliveryId)).thenReturn(deliveryVO);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+ Mockito.doNothing().when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, deliveryId, null, null, null, null, false);
+ Assert.assertNull(result);
+ Mockito.verify(webhookDeliveryDao).findById(deliveryId);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void shouldThrowExceptionForInvalidDeliveryId() {
+ Account caller = Mockito.mock(Account.class);
+ Mockito.when(webhookDeliveryDao.findById(1L)).thenReturn(null);
+ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, 1L, null, null, null, null, false);
+ }
+
+ @Test
+ public void shouldSkipWebhookAccessCheckWhenWebhookNotFound() {
+ Account caller = Mockito.mock(Account.class);
+ Long deliveryId = 1L;
+ WebhookDeliveryVO deliveryVO = Mockito.mock(WebhookDeliveryVO.class);
+ Mockito.when(deliveryVO.getWebhookId()).thenReturn(1L);
+ Mockito.when(webhookDeliveryDao.findById(deliveryId)).thenReturn(deliveryVO);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(null);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, deliveryId, null, null, null, null, false);
+ Assert.assertNull(result);
+ Mockito.verify(accountManager, Mockito.never()).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any());
+ }
+
+ @Test
+ public void shouldValidateWebhookIdAndReturnNull() {
+ Account caller = Mockito.mock(Account.class);
+ Long webhookId = 1L;
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookDao.findById(webhookId)).thenReturn(webhookVO);
+ Mockito.doNothing().when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, webhookId, null, null, null, false);
+ Assert.assertNull(result);
+ Mockito.verify(webhookDao).findById(webhookId);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void shouldThrowExceptionForInvalidWebhookId() {
+ Account caller = Mockito.mock(Account.class);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(null);
+ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null, false);
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void shouldThrowExceptionWhenDeliveryAccessDenied() {
+ Account caller = Mockito.mock(Account.class);
+ Long deliveryId = 1L;
+ WebhookDeliveryVO deliveryVO = Mockito.mock(WebhookDeliveryVO.class);
+ Mockito.when(deliveryVO.getWebhookId()).thenReturn(1L);
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookDeliveryDao.findById(deliveryId)).thenReturn(deliveryVO);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+ Mockito.doThrow(PermissionDeniedException.class).when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, deliveryId, null, null, null, null, false);
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void shouldThrowExceptionWhenWebhookAccessDenied() {
+ Account caller = Mockito.mock(Account.class);
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+ Mockito.doThrow(PermissionDeniedException.class).when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null, false);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void shouldThrowExceptionWhenEndDateBeforeStartDate() {
+ Account caller = Mockito.mock(Account.class);
+ Date endDate = new Date(100);
+ Date startDate = new Date(200);
+ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, null, startDate, endDate, false);
+ }
+
+ @Test
+ public void shouldValidateDateRangeWhenDatesAreValid() {
+ Account caller = Mockito.mock(Account.class);
+ Date startDate = new Date(100);
+ Date endDate = new Date(200);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, null, startDate, endDate, false);
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void shouldIgnoreDateValidationWhenOnlyStartDateProvided() {
+ Account caller = Mockito.mock(Account.class);
+ Date startDate = new Date(200);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, null, startDate, null, false);
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void shouldIgnoreDateValidationWhenOnlyEndDateProvided() {
+ Account caller = Mockito.mock(Account.class);
+ Date endDate = new Date(100);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, null, null, endDate, false);
+ Assert.assertNull(result);
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void shouldThrowExceptionWhenNonAdminAccessesManagementServer() {
+ Account caller = Mockito.mock(Account.class);
+ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null, false);
+ }
+
+ @Test
+ public void shouldReturnManagementServerWhenAdminAccessesValidServer() {
+ Account caller = Mockito.mock(Account.class);
+ Long managementServerId = 1L;
+ ManagementServerHostVO managementServerHostVO = Mockito.mock(ManagementServerHostVO.class);
+ Mockito.when(managementServerHostDao.findById(managementServerId)).thenReturn(managementServerHostVO);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, managementServerId, null, null, true);
+ Assert.assertNotNull(result);
+ Assert.assertEquals(managementServerHostVO, result);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void shouldThrowExceptionForInvalidManagementServerId() {
+ Account caller = Mockito.mock(Account.class);
+ Mockito.when(managementServerHostDao.findById(1L)).thenReturn(null);
+ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, null, 1L, null, null, true);
+ }
+
+ @Test
+ public void shouldValidateDeliveryAndManagementServer() {
+ Account caller = Mockito.mock(Account.class);
+ Long deliveryId = 1L;
+ Long managementServerId = 1L;
+ WebhookDeliveryVO deliveryVO = Mockito.mock(WebhookDeliveryVO.class);
+ Mockito.when(deliveryVO.getWebhookId()).thenReturn(1L);
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ ManagementServerHostVO managementServerHostVO = Mockito.mock(ManagementServerHostVO.class);
+ Mockito.when(webhookDeliveryDao.findById(deliveryId)).thenReturn(deliveryVO);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+ Mockito.doNothing().when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ Mockito.when(managementServerHostDao.findById(managementServerId)).thenReturn(managementServerHostVO);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, deliveryId, null, managementServerId, null, null, true);
+ Assert.assertNotNull(result);
Assert.assertEquals(managementServerHostVO, result);
}
@Test
+ public void basicWebhookDeliveryApiCheckValidatesWebhookDeliveryAccess() {
+ Account caller = Mockito.mock(Account.class);
+ WebhookDeliveryVO webhookDeliveryVO = Mockito.mock(WebhookDeliveryVO.class);
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookDeliveryDao.findById(1L)).thenReturn(webhookDeliveryVO);
+ Mockito.when(webhookDeliveryVO.getWebhookId()).thenReturn(1L);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+ Mockito.doNothing().when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
- webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, 1L, null, null, null, null);
++ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, 1L, null, null, null, null, false);
+ Mockito.verify(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ }
+
+ @Test
+ public void basicWebhookDeliveryApiCheckValidatesWebhookAccess() {
+ Account caller = Mockito.mock(Account.class);
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+ Mockito.doNothing().when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
- webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null);
++ webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null, false);
+ Mockito.verify(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ }
+
+ @Test
+ public void createWebhookDeliveryResponsePopulatesAllFieldsCorrectly() {
+ WebhookDeliveryJoinVO webhookDeliveryVO = Mockito.mock(WebhookDeliveryJoinVO.class);
+ Mockito.when(webhookDeliveryVO.getUuid()).thenReturn("uuid");
+ Mockito.when(webhookDeliveryVO.getEventUuid()).thenReturn("eventUuid");
+ Mockito.when(webhookDeliveryVO.getEventType()).thenReturn("eventType");
+ Mockito.when(webhookDeliveryVO.getWebhookUuId()).thenReturn("webhookUuid");
+ Mockito.when(webhookDeliveryVO.getWebhookName()).thenReturn("webhookName");
+ Mockito.when(webhookDeliveryVO.getManagementServerUuId()).thenReturn("managementServerUuid");
+ Mockito.when(webhookDeliveryVO.getManagementServerName()).thenReturn("managementServerName");
+ Mockito.when(webhookDeliveryVO.getHeaders()).thenReturn("headers");
+ Mockito.when(webhookDeliveryVO.getPayload()).thenReturn("payload");
+ Mockito.when(webhookDeliveryVO.isSuccess()).thenReturn(true);
+ Mockito.when(webhookDeliveryVO.getResponse()).thenReturn("response");
+ Mockito.when(webhookDeliveryVO.getStartTime()).thenReturn(new Date(1000));
+ Mockito.when(webhookDeliveryVO.getEndTime()).thenReturn(new Date(2000));
+
+ WebhookDeliveryResponse response = webhookApiServiceImpl.createWebhookDeliveryResponse(webhookDeliveryVO);
+
+ Assert.assertEquals("webhookdelivery", ReflectionTestUtils.getField(response, "objectName"));
+ Assert.assertEquals("uuid", ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals("eventUuid", ReflectionTestUtils.getField(response, "eventId"));
+ Assert.assertEquals("eventType", ReflectionTestUtils.getField(response, "eventType"));
+ Assert.assertEquals("webhookUuid", ReflectionTestUtils.getField(response, "webhookId"));
+ Assert.assertEquals("webhookName", ReflectionTestUtils.getField(response, "webhookName"));
+ Assert.assertEquals("managementServerUuid", ReflectionTestUtils.getField(response, "managementServerId"));
+ Assert.assertEquals("managementServerName", ReflectionTestUtils.getField(response, "managementServerName"));
+ Assert.assertEquals("headers", ReflectionTestUtils.getField(response, "headers"));
+ Assert.assertEquals("payload", ReflectionTestUtils.getField(response, "payload"));
+ Assert.assertTrue((Boolean) ReflectionTestUtils.getField(response, "success"));
+ Assert.assertEquals("response", ReflectionTestUtils.getField(response, "response"));
+ Assert.assertEquals(new Date(1000), ReflectionTestUtils.getField(response, "startTime"));
+ Assert.assertEquals(new Date(2000), ReflectionTestUtils.getField(response, "endTime"));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void createWebhookDeliveryResponseThrowsExceptionForNullInput() {
+ webhookApiServiceImpl.createWebhookDeliveryResponse(null);
+ }
+
+ @Test
+ public void createTestWebhookDeliveryResponsePopulatesAllFieldsCorrectly() {
+ WebhookDelivery webhookDelivery = Mockito.mock(WebhookDelivery.class);
+ Mockito.when(webhookDelivery.getUuid()).thenReturn("uuid");
+ Mockito.when(webhookDelivery.getManagementServerId()).thenReturn(1L);
+ Mockito.when(webhookDelivery.getHeaders()).thenReturn("headers");
+ Mockito.when(webhookDelivery.getPayload()).thenReturn("payload");
+ Mockito.when(webhookDelivery.isSuccess()).thenReturn(true);
+ Mockito.when(webhookDelivery.getResponse()).thenReturn("response");
+ Mockito.when(webhookDelivery.getStartTime()).thenReturn(new Date(1000));
+ Mockito.when(webhookDelivery.getEndTime()).thenReturn(new Date(2000));
+
+ Webhook webhook = Mockito.mock(Webhook.class);
+ Mockito.when(webhook.getUuid()).thenReturn("webhookUuid");
+ Mockito.when(webhook.getName()).thenReturn("webhookName");
+
+ ManagementServerHostVO msHost = Mockito.mock(ManagementServerHostVO.class);
+ Mockito.when(msHost.getUuid()).thenReturn("managementServerUuid");
+ Mockito.when(msHost.getName()).thenReturn("managementServerName");
+ Mockito.when(managementServerHostDao.findByMsid(1L)).thenReturn(msHost);
+
+ WebhookDeliveryResponse response = webhookApiServiceImpl.createTestWebhookDeliveryResponse(webhookDelivery, webhook);
+
+ Assert.assertEquals("webhookdelivery", ReflectionTestUtils.getField(response, "objectName"));
+ Assert.assertEquals("uuid", ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals(WebhookDelivery.TEST_EVENT_TYPE, ReflectionTestUtils.getField(response, "eventType"));
+ Assert.assertEquals("webhookUuid", ReflectionTestUtils.getField(response, "webhookId"));
+ Assert.assertEquals("webhookName", ReflectionTestUtils.getField(response, "webhookName"));
+ Assert.assertEquals("managementServerUuid", ReflectionTestUtils.getField(response, "managementServerId"));
+ Assert.assertEquals("managementServerName", ReflectionTestUtils.getField(response, "managementServerName"));
+ Assert.assertEquals("headers", ReflectionTestUtils.getField(response, "headers"));
+ Assert.assertEquals("payload", ReflectionTestUtils.getField(response, "payload"));
+ Assert.assertTrue((Boolean) ReflectionTestUtils.getField(response, "success"));
+ Assert.assertEquals("response", ReflectionTestUtils.getField(response, "response"));
+ Assert.assertEquals(new Date(1000), ReflectionTestUtils.getField(response, "startTime"));
+ Assert.assertEquals(new Date(2000), ReflectionTestUtils.getField(response, "endTime"));
+ }
+
+ @Test
+ public void createTestWebhookDeliveryResponseHandlesNullWebhook() {
+ WebhookDelivery webhookDelivery = Mockito.mock(WebhookDelivery.class);
+ Mockito.when(webhookDelivery.getUuid()).thenReturn("uuid");
+ Mockito.when(webhookDelivery.getManagementServerId()).thenReturn(1L);
+ Mockito.when(webhookDelivery.getHeaders()).thenReturn("headers");
+ Mockito.when(webhookDelivery.getPayload()).thenReturn("payload");
+ Mockito.when(webhookDelivery.isSuccess()).thenReturn(true);
+ Mockito.when(webhookDelivery.getResponse()).thenReturn("response");
+ Mockito.when(webhookDelivery.getStartTime()).thenReturn(new Date(1000));
+ Mockito.when(webhookDelivery.getEndTime()).thenReturn(new Date(2000));
+
+ ManagementServerHostVO msHost = Mockito.mock(ManagementServerHostVO.class);
+ Mockito.when(msHost.getUuid()).thenReturn("managementServerUuid");
+ Mockito.when(msHost.getName()).thenReturn("managementServerName");
+ Mockito.when(managementServerHostDao.findByMsid(1L)).thenReturn(msHost);
+
+ WebhookDeliveryResponse response = webhookApiServiceImpl.createTestWebhookDeliveryResponse(webhookDelivery, null);
+
+ Assert.assertEquals("webhookdelivery", ReflectionTestUtils.getField(response, "objectName"));
+ Assert.assertEquals("uuid", ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals(WebhookDelivery.TEST_EVENT_TYPE, ReflectionTestUtils.getField(response, "eventType"));
+ Assert.assertNull(ReflectionTestUtils.getField(response, "webhookId"));
+ Assert.assertNull(ReflectionTestUtils.getField(response, "webhookName"));
+ Assert.assertEquals("managementServerUuid", ReflectionTestUtils.getField(response, "managementServerId"));
+ Assert.assertEquals("managementServerName", ReflectionTestUtils.getField(response, "managementServerName"));
+ Assert.assertEquals("headers", ReflectionTestUtils.getField(response, "headers"));
+ Assert.assertEquals("payload", ReflectionTestUtils.getField(response, "payload"));
+ Assert.assertTrue((Boolean) ReflectionTestUtils.getField(response, "success"));
+ Assert.assertEquals("response", ReflectionTestUtils.getField(response, "response"));
+ Assert.assertEquals(new Date(1000), ReflectionTestUtils.getField(response, "startTime"));
+ Assert.assertEquals(new Date(2000), ReflectionTestUtils.getField(response, "endTime"));
+ }
+
+ @Test
+ public void createTestWebhookDeliveryResponseHandlesNullManagementServer() {
+ WebhookDelivery webhookDelivery = Mockito.mock(WebhookDelivery.class);
+ Mockito.when(webhookDelivery.getUuid()).thenReturn("uuid");
+ Mockito.when(webhookDelivery.getManagementServerId()).thenReturn(1L);
+ Mockito.when(webhookDelivery.getHeaders()).thenReturn("headers");
+ Mockito.when(webhookDelivery.getPayload()).thenReturn("payload");
+ Mockito.when(webhookDelivery.isSuccess()).thenReturn(true);
+ Mockito.when(webhookDelivery.getResponse()).thenReturn("response");
+ Mockito.when(webhookDelivery.getStartTime()).thenReturn(new Date(1000));
+ Mockito.when(webhookDelivery.getEndTime()).thenReturn(new Date(2000));
+
+ Webhook webhook = Mockito.mock(Webhook.class);
+ Mockito.when(webhook.getUuid()).thenReturn("webhookUuid");
+ Mockito.when(webhook.getName()).thenReturn("webhookName");
+
+ Mockito.when(managementServerHostDao.findByMsid(1L)).thenReturn(null);
+
+ WebhookDeliveryResponse response = webhookApiServiceImpl.createTestWebhookDeliveryResponse(webhookDelivery, webhook);
+
+ Assert.assertEquals("webhookdelivery", ReflectionTestUtils.getField(response, "objectName"));
+ Assert.assertEquals("uuid", ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals(WebhookDelivery.TEST_EVENT_TYPE, ReflectionTestUtils.getField(response, "eventType"));
+ Assert.assertEquals("webhookUuid", ReflectionTestUtils.getField(response, "webhookId"));
+ Assert.assertEquals("webhookName", ReflectionTestUtils.getField(response, "webhookName"));
+ Assert.assertNull(ReflectionTestUtils.getField(response, "managementServerId"));
+ Assert.assertNull(ReflectionTestUtils.getField(response, "managementServerName"));
+ Assert.assertEquals("headers", ReflectionTestUtils.getField(response, "headers"));
+ Assert.assertEquals("payload", ReflectionTestUtils.getField(response, "payload"));
+ Assert.assertTrue((Boolean) ReflectionTestUtils.getField(response, "success"));
+ Assert.assertEquals("response", ReflectionTestUtils.getField(response, "response"));
+ Assert.assertEquals(new Date(1000), ReflectionTestUtils.getField(response, "startTime"));
+ Assert.assertEquals(new Date(2000), ReflectionTestUtils.getField(response, "endTime"));
+ }
+
+ @Test
+ public void getOwnerReturnsFinalizedOwnerForValidInput() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+ Account finalizedOwner = Mockito.mock(Account.class);
+
+ Mockito.when(cmd.getAccountName()).thenReturn("accountName");
+ Mockito.when(cmd.getDomainId()).thenReturn(1L);
+ Mockito.when(cmd.getProjectId()).thenReturn(2L);
+ Mockito.when(accountManager.finalizeOwner(caller, "accountName", 1L, 2L)).thenReturn(finalizedOwner);
+
+ Account result = webhookApiServiceImpl.getOwner(cmd);
+
+ Assert.assertEquals(finalizedOwner, result);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void getOwnerThrowsExceptionForInvalidAccount() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+
+ Mockito.when(cmd.getAccountName()).thenReturn("invalidAccount");
+ Mockito.when(cmd.getDomainId()).thenReturn(1L);
+ Mockito.when(cmd.getProjectId()).thenReturn(null);
+ Mockito.when(accountManager.finalizeOwner(caller, "invalidAccount", 1L, null))
+ .thenThrow(new InvalidParameterValueException("Invalid account"));
+
+ webhookApiServiceImpl.getOwner(cmd);
+ }
+
+ @Test
+ public void createWebhookFilterResponsePopulatesAllFieldsCorrectly() {
+ WebhookFilter webhookFilter = Mockito.mock(WebhookFilter.class);
+ Mockito.when(webhookFilter.getUuid()).thenReturn("filterUuid");
+ Mockito.when(webhookFilter.getType()).thenReturn(WebhookFilter.Type.EventType);
+ Mockito.when(webhookFilter.getMode()).thenReturn(WebhookFilter.Mode.Include);
+ Mockito.when(webhookFilter.getMatchType()).thenReturn(WebhookFilter.MatchType.Exact);
+ Mockito.when(webhookFilter.getValue()).thenReturn("value");
+ Mockito.when(webhookFilter.getCreated()).thenReturn(new Date(1000));
+
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookVO.getUuid()).thenReturn("webhookUuid");
+ Mockito.when(webhookVO.getName()).thenReturn("webhookName");
+
+ WebhookFilterResponse response = webhookApiServiceImpl.createWebhookFilterResponse(webhookFilter, webhookVO);
+
+ Assert.assertEquals("webhookfilter", ReflectionTestUtils.getField(response, "objectName"));
+ Assert.assertEquals("filterUuid", ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals("webhookUuid", ReflectionTestUtils.getField(response, "webhookId"));
+ Assert.assertEquals("webhookName", ReflectionTestUtils.getField(response, "webhookName"));
+ Assert.assertEquals("EventType", ReflectionTestUtils.getField(response, "type"));
+ Assert.assertEquals("Include", ReflectionTestUtils.getField(response, "mode"));
+ Assert.assertEquals("Exact", ReflectionTestUtils.getField(response, "matchType"));
+ Assert.assertEquals("value", ReflectionTestUtils.getField(response, "value"));
+ Assert.assertEquals(new Date(1000), ReflectionTestUtils.getField(response, "created"));
+ }
+
+ @Test
+ public void createWebhookFilterResponseHandlesNullWebhookVO() {
+ WebhookFilter webhookFilter = Mockito.mock(WebhookFilter.class);
+ Mockito.when(webhookFilter.getUuid()).thenReturn("filterUuid");
+ Mockito.when(webhookFilter.getWebhookId()).thenReturn(1L);
+ Mockito.when(webhookFilter.getType()).thenReturn(WebhookFilter.Type.EventType);
+ Mockito.when(webhookFilter.getMode()).thenReturn(WebhookFilter.Mode.Include);
+ Mockito.when(webhookFilter.getMatchType()).thenReturn(WebhookFilter.MatchType.Exact);
+ Mockito.when(webhookFilter.getValue()).thenReturn("value");
+ Mockito.when(webhookFilter.getCreated()).thenReturn(new Date(1000));
+
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhookVO.getUuid()).thenReturn("webhookUuid");
+ Mockito.when(webhookVO.getName()).thenReturn("webhookName");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+
+ WebhookFilterResponse response = webhookApiServiceImpl.createWebhookFilterResponse(webhookFilter, null);
+
+ Assert.assertEquals("webhookfilter", ReflectionTestUtils.getField(response, "objectName"));
+ Assert.assertEquals("filterUuid", ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals("webhookUuid", ReflectionTestUtils.getField(response, "webhookId"));
+ Assert.assertEquals("webhookName", ReflectionTestUtils.getField(response, "webhookName"));
+ Assert.assertEquals("EventType", ReflectionTestUtils.getField(response, "type"));
+ Assert.assertEquals("Include", ReflectionTestUtils.getField(response, "mode"));
+ Assert.assertEquals("Exact", ReflectionTestUtils.getField(response, "matchType"));
+ Assert.assertEquals("value", ReflectionTestUtils.getField(response, "value"));
+ Assert.assertEquals(new Date(1000), ReflectionTestUtils.getField(response, "created"));
+ }
+
+ @Test
+ public void createWebhookFilterResponseHandlesNullWebhookFromDao() {
+ WebhookFilter webhookFilter = Mockito.mock(WebhookFilter.class);
+ Mockito.when(webhookFilter.getUuid()).thenReturn("filterUuid");
+ Mockito.when(webhookFilter.getWebhookId()).thenReturn(1L);
+ Mockito.when(webhookFilter.getType()).thenReturn(WebhookFilter.Type.EventType);
+ Mockito.when(webhookFilter.getMode()).thenReturn(WebhookFilter.Mode.Include);
+ Mockito.when(webhookFilter.getMatchType()).thenReturn(WebhookFilter.MatchType.Exact);
+ Mockito.when(webhookFilter.getValue()).thenReturn("value");
+ Mockito.when(webhookFilter.getCreated()).thenReturn(new Date(1000));
+
+ Mockito.when(webhookDao.findById(1L)).thenReturn(null);
+
+ WebhookFilterResponse response = webhookApiServiceImpl.createWebhookFilterResponse(webhookFilter, null);
+
+ Assert.assertEquals("webhookfilter", ReflectionTestUtils.getField(response, "objectName"));
+ Assert.assertEquals("filterUuid", ReflectionTestUtils.getField(response, "id"));
+ Assert.assertNull(ReflectionTestUtils.getField(response, "webhookId"));
+ Assert.assertNull(ReflectionTestUtils.getField(response, "webhookName"));
+ Assert.assertEquals("EventType", ReflectionTestUtils.getField(response, "type"));
+ Assert.assertEquals("Include", ReflectionTestUtils.getField(response, "mode"));
+ Assert.assertEquals("Exact", ReflectionTestUtils.getField(response, "matchType"));
+ Assert.assertEquals("value", ReflectionTestUtils.getField(response, "value"));
+ Assert.assertEquals(new Date(1000), ReflectionTestUtils.getField(response, "created"));
+ }
+
+ @Test
+ public void listWebhooksReturnsEmptyResponseForNoWebhooks() {
+ ListWebhooksCmd cmd = Mockito.mock(ListWebhooksCmd.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getState()).thenReturn(null);
+ Mockito.when(cmd.getName()).thenReturn(null);
+ Mockito.when(cmd.getKeyword()).thenReturn(null);
+ Mockito.when(cmd.getScope()).thenReturn(null);
+ Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+ Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+ SearchBuilder<WebhookJoinVO> sb = Mockito.mock(SearchBuilder.class);
+ Mockito.when(sb.entity()).thenReturn(Mockito.mock(WebhookJoinVO.class));
+ SearchCriteria<WebhookJoinVO> sc = Mockito.mock(SearchCriteria.class);
+ Mockito.when(sb.create()).thenReturn(sc);
+ Mockito.when(webhookJoinDao.createSearchBuilder()).thenReturn(sb);
+ Mockito.when(webhookJoinDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(List.of(), 0));
+
+ ListResponse<WebhookResponse> response = webhookApiServiceImpl.listWebhooks(cmd);
+
+ Assert.assertTrue(response.getResponses().isEmpty());
+ Assert.assertEquals(0, (int) response.getCount());
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void listWebhooksThrowsExceptionForInvalidScope() {
+ ListWebhooksCmd cmd = Mockito.mock(ListWebhooksCmd.class);
+
+ Mockito.when(cmd.getScope()).thenReturn("InvalidScope");
+
+ webhookApiServiceImpl.listWebhooks(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void listWebhooksThrowsExceptionForInvalidState() {
+ ListWebhooksCmd cmd = Mockito.mock(ListWebhooksCmd.class);
+
+ Mockito.when(cmd.getState()).thenReturn("InvalidState");
+
+ webhookApiServiceImpl.listWebhooks(cmd);
+ }
+
+ @Test
+ public void listWebhooksFiltersByValidScopeAndState() {
+ ListWebhooksCmd cmd = Mockito.mock(ListWebhooksCmd.class);
- Mockito.when(caller.getType()).thenReturn(Account.Type.DOMAIN_ADMIN);
++ Mockito.when(accountManager.isAdmin(Mockito.anyLong())).thenReturn(true);
+ WebhookJoinVO webhook = Mockito.mock(WebhookJoinVO.class);
+ Mockito.when(webhook.getState()).thenReturn(Webhook.State.Enabled);
+ Mockito.when(webhook.getScope()).thenReturn(Webhook.Scope.Domain);
+
+ Mockito.when(cmd.getScope()).thenReturn("Domain");
+ Mockito.when(cmd.getState()).thenReturn("Enabled");
+ Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+ Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+ SearchBuilder<WebhookJoinVO> sb = Mockito.mock(SearchBuilder.class);
+ Mockito.when(sb.entity()).thenReturn(Mockito.mock(WebhookJoinVO.class));
+ SearchCriteria<WebhookJoinVO> sc = Mockito.mock(SearchCriteria.class);
+ Mockito.when(sb.create()).thenReturn(sc);
+ Mockito.when(webhookJoinDao.createSearchBuilder()).thenReturn(sb);
+ Mockito.when(webhookJoinDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(List.of(webhook), 1));
+
+ ListResponse<WebhookResponse> response = webhookApiServiceImpl.listWebhooks(cmd);
+
+ Assert.assertEquals(1, (int) response.getCount());
+ Assert.assertEquals(1, response.getResponses().size());
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void listWebhooksThrowsExceptionForUnauthorizedScope() {
+ ListWebhooksCmd cmd = Mockito.mock(ListWebhooksCmd.class);
+
+ Mockito.when(cmd.getScope()).thenReturn("Global");
- Mockito.when(caller.getType()).thenReturn(Account.Type.NORMAL);
+
+ webhookApiServiceImpl.listWebhooks(cmd);
+ }
+
+ @Test
+ public void createWebhookCreatesWebhookSuccessfully() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookJoinVO webhookJoinVO = Mockito.mock(WebhookJoinVO.class);
+ Mockito.when(webhookJoinVO.getState()).thenReturn(Webhook.State.Enabled);
+ Mockito.when(webhookJoinVO.getScope()).thenReturn(Webhook.Scope.Local);
+
+ Mockito.when(cmd.getName()).thenReturn("webhookName");
+ Mockito.when(cmd.getDescription()).thenReturn("webhookDescription");
+ Mockito.when(cmd.getPayloadUrl()).thenReturn("https://example.com");
+ Mockito.when(cmd.getSecretKey()).thenReturn("secretKey");
+ Mockito.when(cmd.isSslVerification()).thenReturn(true);
+ Mockito.when(cmd.getScope()).thenReturn("Local");
+ Mockito.when(cmd.getState()).thenReturn("Enabled");
- Mockito.when(caller.getType()).thenReturn(Account.Type.ADMIN);
+ Mockito.when(caller.getDomainId()).thenReturn(1L);
+ Mockito.when(webhookDao.persist(Mockito.any(WebhookVO.class))).thenReturn(webhook);
+ Mockito.when(webhook.getId()).thenReturn(1L);
+ Mockito.when(webhookJoinDao.findById(1L)).thenReturn(webhookJoinVO);
+ Mockito.doReturn(caller).when(webhookApiServiceImpl).getOwner(cmd);
+
+ WebhookResponse response = webhookApiServiceImpl.createWebhook(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals("webhook", ReflectionTestUtils.getField(response, "objectName"));
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void createWebhookThrowsExceptionForInvalidScope() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+
+ Mockito.when(cmd.getScope()).thenReturn("InvalidScope");
++ Mockito.doReturn(caller).when(webhookApiServiceImpl).getOwner(cmd);
+
+ webhookApiServiceImpl.createWebhook(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void createWebhookThrowsExceptionForInvalidState() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+
+ Mockito.when(cmd.getState()).thenReturn("InvalidState");
++ Mockito.doReturn(caller).when(webhookApiServiceImpl).getOwner(cmd);
+
+ webhookApiServiceImpl.createWebhook(cmd);
+ }
+
- @Test(expected = IllegalArgumentException.class)
++ @Test(expected = InvalidParameterValueException.class)
+ public void createWebhookThrowsExceptionForInvalidUrl() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+
+ Mockito.when(cmd.getPayloadUrl()).thenReturn("invalid-url");
+ Mockito.doReturn(caller).when(webhookApiServiceImpl).getOwner(cmd);
+
+ webhookApiServiceImpl.createWebhook(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void createWebhookThrowsExceptionForNonHttpsWithSslVerification() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+
+ Mockito.when(cmd.getPayloadUrl()).thenReturn("http://example.com");
+ Mockito.when(cmd.isSslVerification()).thenReturn(true);
+ Mockito.doReturn(caller).when(webhookApiServiceImpl).getOwner(cmd);
- Mockito.doNothing().when(webhookApiServiceImpl)
- .validateWebhookOwnerPayloadUrl(caller, "http://example.com", null);
+
+ webhookApiServiceImpl.createWebhook(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void createWebhookThrowsExceptionForDuplicatePayloadUrl() {
+ CreateWebhookCmd cmd = Mockito.mock(CreateWebhookCmd.class);
+ WebhookVO existingWebhook = Mockito.mock(WebhookVO.class);
+
+ Mockito.when(cmd.getPayloadUrl()).thenReturn("https://example.com");
+ Mockito.when(caller.getId()).thenReturn(1L);
+ Mockito.doReturn(caller).when(webhookApiServiceImpl).getOwner(cmd);
+ Mockito.when(webhookDao.findByAccountAndPayloadUrl(1L, "https://example.com")).thenReturn(existingWebhook);
+
+ webhookApiServiceImpl.createWebhook(cmd);
+ }
+
+ @Test
+ public void updateWebhookUpdatesAllFieldsSuccessfully() {
+ UpdateWebhookCmd cmd = Mockito.mock(UpdateWebhookCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhook.getId()).thenReturn(1L);
+ Mockito.when(webhook.getPayloadUrl()).thenReturn("http://abc.xyz");
+ String updatedUrl = "https://cloudstack.apache.org/";
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getName()).thenReturn("Updated Name");
+ Mockito.when(cmd.getDescription()).thenReturn("Updated Description");
+ Mockito.when(cmd.getPayloadUrl()).thenReturn(updatedUrl);
+ Mockito.when(cmd.getSecretKey()).thenReturn("UpdatedSecretKey");
+ Mockito.when(cmd.isSslVerification()).thenReturn(true);
+ Mockito.when(cmd.getScope()).thenReturn("Local");
+ Mockito.when(cmd.getState()).thenReturn("Enabled");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+ Mockito.when(webhook.getAccountId()).thenReturn(1L);
+ Mockito.when(accountManager.getAccount(1L)).thenReturn(caller);
+ Mockito.when(webhookDao.update(1L, webhook)).thenReturn(true);
+ Mockito.doReturn(Mockito.mock(WebhookResponse.class)).when(webhookApiServiceImpl)
+ .createWebhookResponse(1L);
+
+ WebhookResponse response = webhookApiServiceImpl.updateWebhook(cmd);
+
+
+ Assert.assertNotNull(response);
+ Mockito.verify(webhook).setName("Updated Name");
+ Mockito.verify(webhook).setDescription("Updated Description");
+ Mockito.verify(webhook).setPayloadUrl(updatedUrl);
+ Mockito.verify(webhook).setSecretKey("UpdatedSecretKey");
+ Mockito.verify(webhook).setSslVerification(true);
+ Mockito.verify(webhook).setScope(Webhook.Scope.Local);
+ Mockito.verify(webhook).setState(Webhook.State.Enabled);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void updateWebhookThrowsExceptionForInvalidState() {
+ UpdateWebhookCmd cmd = Mockito.mock(UpdateWebhookCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getState()).thenReturn("InvalidState");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+
+ webhookApiServiceImpl.updateWebhook(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void updateWebhookThrowsExceptionForInvalidScope() {
+ UpdateWebhookCmd cmd = Mockito.mock(UpdateWebhookCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getScope()).thenReturn("InvalidScope");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+ Mockito.when(webhook.getAccountId()).thenReturn(1L);
+ Mockito.when(accountManager.getAccount(1L)).thenReturn(caller);
+
+ webhookApiServiceImpl.updateWebhook(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void updateWebhookThrowsExceptionForNonHttpsWithSslVerification() {
+ UpdateWebhookCmd cmd = Mockito.mock(UpdateWebhookCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhook.getPayloadUrl()).thenReturn("http://abc.xyz");
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getPayloadUrl()).thenReturn("http://cloudstack.apache.org/");
+ Mockito.when(cmd.isSslVerification()).thenReturn(true);
+ Mockito.when(webhook.getAccountId()).thenReturn(1L);
+ Mockito.when(accountManager.getAccount(1L)).thenReturn(caller);
- Mockito.doNothing().when(webhookApiServiceImpl)
- .validateWebhookOwnerPayloadUrl(caller, "http://cloudstack.apache.org/", webhook);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+
+ webhookApiServiceImpl.updateWebhook(cmd);
+ }
+
+ @Test
+ public void updateWebhookDoesNotUpdateUnchangedFields() {
+ UpdateWebhookCmd cmd = Mockito.mock(UpdateWebhookCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ Mockito.when(webhook.getId()).thenReturn(1L);
+ Mockito.when(webhook.getPayloadUrl()).thenReturn("http://abc.xyz");
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getName()).thenReturn(null);
+ Mockito.when(cmd.getDescription()).thenReturn(null);
+ Mockito.when(cmd.getPayloadUrl()).thenReturn(null);
+ Mockito.when(cmd.getSecretKey()).thenReturn(null);
+ Mockito.when(cmd.isSslVerification()).thenReturn(null);
+ Mockito.when(cmd.getScope()).thenReturn(null);
+ Mockito.when(cmd.getState()).thenReturn(null);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
++ Mockito.when(accountManager.getAccount(Mockito.anyLong())).thenReturn(caller);
+ Mockito.doReturn(Mockito.mock(WebhookResponse.class)).when(webhookApiServiceImpl)
+ .createWebhookResponse(1L);
+
+ WebhookResponse response = webhookApiServiceImpl.updateWebhook(cmd);
+
+ Assert.assertNotNull(response);
+ Mockito.verify(webhookDao, Mockito.never()).update(Mockito.anyLong(), Mockito.any());
+ }
+
+ @Test
+ public void listWebhookDeliveriesReturnsEmptyResponseForNoDeliveries() {
+ ListWebhookDeliveriesCmd cmd = Mockito.mock(ListWebhookDeliveriesCmd.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(null);
+ Mockito.when(cmd.getManagementServerId()).thenReturn(null);
+ Mockito.when(cmd.getKeyword()).thenReturn(null);
+ Mockito.when(cmd.getStartDate()).thenReturn(null);
+ Mockito.when(cmd.getEndDate()).thenReturn(null);
+ Mockito.when(cmd.getEventType()).thenReturn(null);
+ Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+ Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+ Mockito.when(webhookDeliveryJoinDao.searchAndCountByListApiParameters(Mockito.any(), Mockito.anyList(),
- Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
++ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
++ Mockito.anyBoolean()))
+ .thenReturn(new Pair<>(List.of(), 0));
+
+ ListResponse<WebhookDeliveryResponse> response = webhookApiServiceImpl.listWebhookDeliveries(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertTrue(response.getResponses().isEmpty());
+ Assert.assertEquals(0, (int) response.getCount());
+ }
+
+ @Test
+ public void listWebhookDeliveriesFiltersByWebhookId() {
+ ListWebhookDeliveriesCmd cmd = Mockito.mock(ListWebhookDeliveriesCmd.class);
+ WebhookDeliveryJoinVO delivery = Mockito.mock(WebhookDeliveryJoinVO.class);
+ WebhookDeliveryResponse deliveryResponse = Mockito.mock(WebhookDeliveryResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(1L);
+ Mockito.when(cmd.getManagementServerId()).thenReturn(null);
+ Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+ Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+ Mockito.when(webhookDeliveryJoinDao.searchAndCountByListApiParameters(Mockito.any(), Mockito.anyList(),
- Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
++ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
++ Mockito.anyBoolean()))
+ .thenReturn(new Pair<>(List.of(delivery), 1));
+ Mockito.doReturn(deliveryResponse).when(webhookApiServiceImpl).createWebhookDeliveryResponse(delivery);
+ Mockito.doReturn(null).when(webhookApiServiceImpl)
- .basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null);
++ .basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null, false);
+
+ ListResponse<WebhookDeliveryResponse> response = webhookApiServiceImpl.listWebhookDeliveries(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(1, response.getResponses().size());
+ Assert.assertEquals(deliveryResponse, response.getResponses().get(0));
+ Assert.assertEquals(1, (int) response.getCount());
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void listWebhookDeliveriesThrowsExceptionForEndDateBeforeStartDate() {
+ ListWebhookDeliveriesCmd cmd = Mockito.mock(ListWebhookDeliveriesCmd.class);
+
+ Mockito.when(cmd.getStartDate()).thenReturn(new Date(System.currentTimeMillis()));
+ Mockito.when(cmd.getEndDate()).thenReturn(new Date(System.currentTimeMillis() - 1000));
+
+ webhookApiServiceImpl.listWebhookDeliveries(cmd);
+ }
+
+ @Test
+ public void listWebhookDeliveriesFiltersByEventType() {
+ ListWebhookDeliveriesCmd cmd = Mockito.mock(ListWebhookDeliveriesCmd.class);
+ WebhookDeliveryJoinVO delivery = Mockito.mock(WebhookDeliveryJoinVO.class);
+ WebhookDeliveryResponse deliveryResponse = Mockito.mock(WebhookDeliveryResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(null);
+ Mockito.when(cmd.getManagementServerId()).thenReturn(null);
+ Mockito.when(cmd.getEventType()).thenReturn("EventType");
+ Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+ Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+ Mockito.when(webhookDeliveryJoinDao.searchAndCountByListApiParameters(Mockito.any(), Mockito.anyList(),
- Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
++ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
++ Mockito.anyBoolean()))
+ .thenReturn(new Pair<>(List.of(delivery), 1));
+ Mockito.doReturn(deliveryResponse).when(webhookApiServiceImpl).createWebhookDeliveryResponse(delivery);
+
+ ListResponse<WebhookDeliveryResponse> response = webhookApiServiceImpl.listWebhookDeliveries(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(1, response.getResponses().size());
+ Assert.assertEquals(deliveryResponse, response.getResponses().get(0));
+ Assert.assertEquals(1, (int) response.getCount());
+ }
+
+ @Test
+ public void deleteWebhookDeliveryRemovesDeliveriesSuccessfully() {
+ DeleteWebhookDeliveryCmd cmd = Mockito.mock(DeleteWebhookDeliveryCmd.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(1L);
+ Mockito.when(cmd.getManagementServerId()).thenReturn(null);
+ Mockito.when(cmd.getStartDate()).thenReturn(null);
+ Mockito.when(cmd.getEndDate()).thenReturn(null);
+ Mockito.doReturn(null).when(webhookApiServiceImpl)
- .basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null);
- Mockito.when(webhookDeliveryDao.deleteByDeleteApiParams(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(5);
++ .basicWebhookDeliveryApiCheck(caller, null, 1L, null, null, null, false);
++ Mockito.when(webhookDeliveryDao.deleteByDeleteApiParams(Mockito.any(), Mockito.any(), Mockito.any(),
++ Mockito.any(), Mockito.any(), Mockito.anyBoolean())).thenReturn(5);
+
+ int removed = webhookApiServiceImpl.deleteWebhookDelivery(cmd);
+
+ Assert.assertEquals(5, removed);
- Mockito.verify(webhookDeliveryDao).deleteByDeleteApiParams(null, 1L, null, null, null);
++ Mockito.verify(webhookDeliveryDao).deleteByDeleteApiParams(null, List.of(1L), null, null, null, false);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void deleteWebhookDeliveryThrowsExceptionForInvalidDates() {
+ DeleteWebhookDeliveryCmd cmd = Mockito.mock(DeleteWebhookDeliveryCmd.class);
+ Mockito.when(cmd.getStartDate()).thenReturn(new Date(System.currentTimeMillis()));
+ Mockito.when(cmd.getEndDate()).thenReturn(new Date(System.currentTimeMillis() - 1000));
+
+ webhookApiServiceImpl.deleteWebhookDelivery(cmd);
+ }
+
+ @Test
+ public void deleteWebhookDeliveryHandlesNoDeliveriesToRemove() {
+ DeleteWebhookDeliveryCmd cmd = Mockito.mock(DeleteWebhookDeliveryCmd.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(1L);
+ Mockito.when(cmd.getManagementServerId()).thenReturn(null);
+ Mockito.when(cmd.getStartDate()).thenReturn(null);
+ Mockito.when(cmd.getEndDate()).thenReturn(null);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(Mockito.mock(WebhookVO.class));
- Mockito.when(webhookDeliveryDao.deleteByDeleteApiParams(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(0);
++ Mockito.when(webhookDeliveryDao.deleteByDeleteApiParams(Mockito.any(), Mockito.any(), Mockito.any(),
++ Mockito.any(), Mockito.any(), Mockito.anyBoolean())).thenReturn(0);
+
+ int removed = webhookApiServiceImpl.deleteWebhookDelivery(cmd);
+
+ Assert.assertEquals(0, removed);
- Mockito.verify(webhookDeliveryDao).deleteByDeleteApiParams(null, 1L, null, null, null);
++ Mockito.verify(webhookDeliveryDao).deleteByDeleteApiParams(null, List.of(1L), null, null, null, false);
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void deleteWebhookDeliveryThrowsExceptionForUnauthorizedAccess() {
+ DeleteWebhookDeliveryCmd cmd = Mockito.mock(DeleteWebhookDeliveryCmd.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(1L);
- Mockito.when(caller.getType()).thenReturn(Account.Type.NORMAL);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(Mockito.mock(WebhookVO.class));
+
+ webhookApiServiceImpl.deleteWebhookDelivery(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void executeWebhookDeliveryThrowsExceptionWhenNoIdentifiersProvided() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(null);
+ Mockito.when(cmd.getPayloadUrl()).thenReturn(null);
+
+ webhookApiServiceImpl.executeWebhookDelivery(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void executeWebhookDeliveryThrowsExceptionWhenBothDeliveryIdAndWebhookIdProvided() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getWebhookId()).thenReturn(2L);
+
+ webhookApiServiceImpl.executeWebhookDelivery(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void executeWebhookDeliveryThrowsExceptionForInvalidDeliveryId() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ Mockito.when(cmd.getId()).thenReturn(1L);
+
+ webhookApiServiceImpl.executeWebhookDelivery(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void executeWebhookDeliveryThrowsExceptionForInvalidWebhookId() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ Mockito.when(cmd.getWebhookId()).thenReturn(1L);
+
+ webhookApiServiceImpl.executeWebhookDelivery(cmd);
+ }
+
+ @Test
+ public void executeWebhookDeliveryExecutesSuccessfullyForValidDeliveryId() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ WebhookDeliveryVO delivery = Mockito.mock(WebhookDeliveryVO.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookDelivery webhookDelivery = Mockito.mock(WebhookDelivery.class);
+ WebhookDeliveryResponse response = Mockito.mock(WebhookDeliveryResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getWebhookId()).thenReturn(null);
+ Mockito.when(webhookDeliveryDao.findById(1L)).thenReturn(delivery);
+ Mockito.when(delivery.getWebhookId()).thenReturn(2L);
+ Mockito.when(webhookDao.findById(2L)).thenReturn(webhook);
- Mockito.when(webhookService.executeWebhookDelivery(delivery, webhook, null)).thenReturn(webhookDelivery);
++ Mockito.when(webhookService.executeWebhookDelivery(delivery, webhook, null, null)).thenReturn(webhookDelivery);
+ Mockito.when(webhookDelivery.getId()).thenReturn(3L);
+ Mockito.when(webhookDeliveryJoinDao.findById(3L)).thenReturn(Mockito.mock(WebhookDeliveryJoinVO.class));
+ Mockito.doReturn(response).when(webhookApiServiceImpl).createWebhookDeliveryResponse(Mockito.any());
+
+ WebhookDeliveryResponse result = webhookApiServiceImpl.executeWebhookDelivery(cmd);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(response, result);
+ }
+
+ @Test
+ public void executeWebhookDeliveryExecutesSuccessfullyForValidWebhookId() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookDelivery webhookDelivery = Mockito.mock(WebhookDelivery.class);
+ WebhookDeliveryResponse response = Mockito.mock(WebhookDeliveryResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(1L);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
- Mockito.when(webhookService.executeWebhookDelivery(null, webhook, null)).thenReturn(webhookDelivery);
++ Mockito.when(webhookService.executeWebhookDelivery(null, webhook, null, null)).thenReturn(webhookDelivery);
+ Mockito.when(webhookDelivery.getId()).thenReturn(WebhookDelivery.ID_DUMMY);
+ Mockito.doReturn(response).when(webhookApiServiceImpl).createTestWebhookDeliveryResponse(webhookDelivery, webhook);
+
+ WebhookDeliveryResponse result = webhookApiServiceImpl.executeWebhookDelivery(cmd);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(response, result);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void executeWebhookDeliveryThrowsExceptionForInvalidPayloadUrl() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ Mockito.when(cmd.getPayloadUrl()).thenReturn("invalid-url");
+
+ webhookApiServiceImpl.executeWebhookDelivery(cmd);
+ }
+
+ @Test
+ public void executeWebhookDeliveryExecutesSuccessfullyForValidPayloadUrl() {
+ ExecuteWebhookDeliveryCmd cmd = Mockito.mock(ExecuteWebhookDeliveryCmd.class);
+ WebhookDelivery webhookDelivery = Mockito.mock(WebhookDelivery.class);
+ WebhookDeliveryResponse response = Mockito.mock(WebhookDeliveryResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(null);
+ Mockito.when(cmd.getPayloadUrl()).thenReturn("https://example.com");
++ Mockito.when(accountManager.finalizeOwner(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
++ .thenReturn(caller);
+ Mockito.when(webhookService.executeWebhookDelivery(Mockito.eq(null), Mockito.any(Webhook.class),
- Mockito.eq(null))).thenReturn(webhookDelivery);
++ Mockito.eq(null), Mockito.any(URI.class))).thenReturn(webhookDelivery);
+ Mockito.when(webhookDelivery.getId()).thenReturn(WebhookDelivery.ID_DUMMY);
+ Mockito.doReturn(response).when(webhookApiServiceImpl).createTestWebhookDeliveryResponse(
+ Mockito.eq(webhookDelivery), Mockito.any(Webhook.class));
+
+ WebhookDeliveryResponse result = webhookApiServiceImpl.executeWebhookDelivery(cmd);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(response, result);
+ }
+
+ @Test
+ public void listWebhookFiltersReturnsEmptyResponseForNoFilters() {
+ ListWebhookFiltersCmd cmd = Mockito.mock(ListWebhookFiltersCmd.class);
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(null);
+ Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+ Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+ Mockito.when(webhookFilterDao.searchBy(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong()))
+ .thenReturn(new Pair<>(List.of(), 0));
+
+ ListResponse<WebhookFilterResponse> response = webhookApiServiceImpl.listWebhookFilters(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertTrue(response.getResponses().isEmpty());
+ }
+
+ @Test
+ public void listWebhookFiltersReturnsFiltersSuccessfully() {
+ ListWebhookFiltersCmd cmd = Mockito.mock(ListWebhookFiltersCmd.class);
+ WebhookFilterVO filter = Mockito.mock(WebhookFilterVO.class);
+ Mockito.when(filter.getWebhookId()).thenReturn(1L);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookFilterResponse filterResponse = Mockito.mock(WebhookFilterResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(null);
+ Mockito.when(cmd.getWebhookId()).thenReturn(1L);
+ Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+ Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+ Mockito.when(webhookFilterDao.searchBy(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong()))
+ .thenReturn(new Pair<>(List.of(filter), 1));
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+ Mockito.doReturn(filterResponse).when(webhookApiServiceImpl).createWebhookFilterResponse(filter, webhook);
+
+ ListResponse<WebhookFilterResponse> response = webhookApiServiceImpl.listWebhookFilters(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(1, response.getResponses().size());
+ Assert.assertEquals(filterResponse, response.getResponses().get(0));
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void addWebhookFilterThrowsExceptionForInvalidWebhookId() {
+ AddWebhookFilterCmd cmd = Mockito.mock(AddWebhookFilterCmd.class);
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(null);
+
+ webhookApiServiceImpl.addWebhookFilter(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void addWebhookFilterThrowsExceptionForInvalidMode() {
+ AddWebhookFilterCmd cmd = Mockito.mock(AddWebhookFilterCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getMode()).thenReturn("InvalidMode");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+
+ webhookApiServiceImpl.addWebhookFilter(cmd);
+ }
+
+ @Test
+ public void addWebhookFilterAddsFilterSuccessfully() {
+ AddWebhookFilterCmd cmd = Mockito.mock(AddWebhookFilterCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookFilterVO filter = Mockito.mock(WebhookFilterVO.class);
+ WebhookFilterResponse filterResponse = Mockito.mock(WebhookFilterResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getType()).thenReturn("EventType");
+ Mockito.when(cmd.getMode()).thenReturn("Include");
+ Mockito.when(cmd.getMatchType()).thenReturn("Exact");
+ Mockito.when(cmd.getValue()).thenReturn("value");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+ Mockito.when(webhookFilterDao.persist(Mockito.any(WebhookFilterVO.class))).thenReturn(filter);
+ Mockito.doReturn(filterResponse).when(webhookApiServiceImpl).createWebhookFilterResponse(filter, webhook);
+
+ WebhookFilterResponse response = webhookApiServiceImpl.addWebhookFilter(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(filterResponse, response);
+ }
+
+ @Test
+ public void addWebhookFilterAddsFilterSuccessfullyEvenWithExisting() {
+ AddWebhookFilterCmd cmd = Mockito.mock(AddWebhookFilterCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookFilterVO filter = Mockito.mock(WebhookFilterVO.class);
+ WebhookFilterVO newFilter = Mockito.mock(WebhookFilterVO.class);
+ WebhookFilterResponse filterResponse = Mockito.mock(WebhookFilterResponse.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(webhook.getId()).thenReturn(1L);
+ Mockito.when(filter.getType()).thenReturn(WebhookFilter.Type.EventType);
+ Mockito.when(filter.getMode()).thenReturn(WebhookFilter.Mode.Exclude);
+ Mockito.when(filter.getMatchType()).thenReturn(WebhookFilter.MatchType.Prefix);
+ Mockito.when(filter.getValue()).thenReturn("value.old");
+ Mockito.when(cmd.getType()).thenReturn("EventType");
+ Mockito.when(cmd.getMode()).thenReturn("Include");
+ Mockito.when(cmd.getMatchType()).thenReturn("Exact");
+ Mockito.when(cmd.getValue()).thenReturn("value");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+ Mockito.when(webhookFilterDao.listByWebhook(1L)).thenReturn(List.of(filter));
+ Mockito.when(webhookFilterDao.persist(Mockito.any(WebhookFilterVO.class))).thenReturn(newFilter);
+ Mockito.doReturn(filterResponse).when(webhookApiServiceImpl).createWebhookFilterResponse(newFilter, webhook);
+
+ WebhookFilterResponse response = webhookApiServiceImpl.addWebhookFilter(cmd);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(filterResponse, response);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void addWebhookFilterConflictsWithExisting() {
+ AddWebhookFilterCmd cmd = Mockito.mock(AddWebhookFilterCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookFilterVO filter = Mockito.mock(WebhookFilterVO.class);
+
+ Mockito.when(webhook.getId()).thenReturn(1L);
+ Mockito.when(filter.getType()).thenReturn(WebhookFilter.Type.EventType);
+ Mockito.when(filter.getMode()).thenReturn(WebhookFilter.Mode.Exclude);
+ Mockito.when(filter.getMatchType()).thenReturn(WebhookFilter.MatchType.Prefix);
+ Mockito.when(filter.getValue()).thenReturn("value");
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getType()).thenReturn("EventType");
+ Mockito.when(cmd.getMode()).thenReturn("Include");
+ Mockito.when(cmd.getMatchType()).thenReturn("Exact");
+ Mockito.when(cmd.getValue()).thenReturn("value.extra");
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+ Mockito.when(webhookFilterDao.listByWebhook(1L)).thenReturn(List.of(filter));
+
+ webhookApiServiceImpl.addWebhookFilter(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void addWebhookFilterSameEventConflictsWithExisting() {
+ AddWebhookFilterCmd cmd = Mockito.mock(AddWebhookFilterCmd.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+ WebhookFilterVO filter = Mockito.mock(WebhookFilterVO.class);
+
+ Mockito.when(webhook.getId()).thenReturn(1L);
+ Mockito.when(filter.getType()).thenReturn(WebhookFilter.Type.EventType);
+ Mockito.when(filter.getMode()).thenReturn(WebhookFilter.Mode.Include);
+ Mockito.when(filter.getMatchType()).thenReturn(WebhookFilter.MatchType.Exact);
+ Mockito.when(filter.getValue()).thenReturn(EventTypes.EVENT_VM_CREATE);
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(cmd.getType()).thenReturn(WebhookFilter.Type.EventType.name());
+ Mockito.when(cmd.getMode()).thenReturn(WebhookFilter.Mode.Exclude.name());
+ Mockito.when(cmd.getMatchType()).thenReturn(WebhookFilter.MatchType.Exact.name());
+ Mockito.when(cmd.getValue()).thenReturn(EventTypes.EVENT_VM_CREATE);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhook);
+ Mockito.when(webhookFilterDao.listByWebhook(1L)).thenReturn(List.of(filter));
+
+ webhookApiServiceImpl.addWebhookFilter(cmd);
+ }
+
+ @Test
+ public void deleteWebhookFilterDeletesFilterSuccessfully() {
+ DeleteWebhookFilterCmd cmd = Mockito.mock(DeleteWebhookFilterCmd.class);
+ WebhookFilterVO filter = Mockito.mock(WebhookFilterVO.class);
+ WebhookVO webhook = Mockito.mock(WebhookVO.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(webhookFilterDao.searchBy(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong()))
+ .thenReturn(new Pair<>(List.of(filter), 1));
+ Mockito.when(webhookDao.findById(Mockito.anyLong())).thenReturn(webhook);
+ Mockito.when(webhookFilterDao.delete(Mockito.anyLong(), Mockito.anyLong())).thenReturn(1);
+
+ int result = webhookApiServiceImpl.deleteWebhookFilter(cmd);
+
+ Assert.assertEquals(1, result);
+ }
+
+ @Test
+ public void deleteWebhookFilterHandlesNoFiltersToDelete() {
+ DeleteWebhookFilterCmd cmd = Mockito.mock(DeleteWebhookFilterCmd.class);
+
+ Mockito.when(cmd.getId()).thenReturn(1L);
+ Mockito.when(webhookFilterDao.searchBy(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong()))
+ .thenReturn(new Pair<>(List.of(), 0));
+
+ int result = webhookApiServiceImpl.deleteWebhookFilter(cmd);
+
+ Assert.assertEquals(0, result);
+ }
++
+ public void shouldValidateAllParametersSuccessfully() {
+ Account caller = Mockito.mock(Account.class);
+ Long deliveryId = 1L;
+ Long managementServerId = 1L;
+ Date startDate = new Date(100);
+ Date endDate = new Date(200);
+ WebhookDeliveryVO deliveryVO = Mockito.mock(WebhookDeliveryVO.class);
+ Mockito.when(deliveryVO.getWebhookId()).thenReturn(1L);
+ WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
+ ManagementServerHostVO managementServerHostVO = Mockito.mock(ManagementServerHostVO.class);
+ Mockito.when(webhookDeliveryDao.findById(deliveryId)).thenReturn(deliveryVO);
+ Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
+ Mockito.doNothing().when(accountManager).checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
+ Mockito.when(managementServerHostDao.findById(managementServerId)).thenReturn(managementServerHostVO);
+ ManagementServerHostVO result = webhookApiServiceImpl.basicWebhookDeliveryApiCheck(caller, deliveryId, null, managementServerId, startDate, endDate, true);
+ Assert.assertNotNull(result);
+ Assert.assertEquals(managementServerHostVO, result);
+ }
}
diff --cc plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java
index cc2a0868fe1,9ebb180b055..3839d7f6bdb
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java
@@@ -52,23 -55,7 +55,12 @@@ import com.cloud.vm.VirtualMachine
@ResourceWrapper(handles = RestoreBackupCommand.class)
public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBackupCommand, Answer, LibvirtComputingResource> {
private static final String BACKUP_TEMP_FILE_PREFIX = "csbackup";
- private static final String MOUNT_COMMAND = "sudo mount -t %s %s %s";
- private static final String UMOUNT_COMMAND = "sudo umount %s";
private static final String FILE_PATH_PLACEHOLDER = "%s/%s";
- private static final String ATTACH_QCOW2_DISK_COMMAND = " virsh attach-disk %s %s %s --driver qemu --subdriver qcow2 --cache none";
- private static final String ATTACH_RAW_DISK_COMMAND = " virsh attach-disk %s %s %s --driver qemu --cache none";
- private static final String ATTACH_RBD_DISK_XML_COMMAND = " virsh attach-device %s /dev/stdin <<EOF%sEOF";
- private static final String CURRRENT_DEVICE = "virsh domblklist --domain %s | tail -n 3 | head -n 1 | awk '{print $1}'";
- private static final String RSYNC_COMMAND = "rsync -az %s %s";
- // Flattens the backing-file chain into a single self-contained qcow2 written to the
- // destination volume path. Used when the source backup is an incremental whose qcow2
- // has a backing reference to its parent (chain set up by nasbackup.sh's qemu-img rebase).
- private static final String QEMU_IMG_FLATTEN_COMMAND = "qemu-img convert -O qcow2 %s %s";
+ // Detects whether a qcow2 file references a parent in its backing-file metadata.
+ // Returns 0 (true) when a backing file is present, 1 when not. Uses --output=json
+ // so the test is robust to qemu-img version differences in human-readable output.
+ private static final String QEMU_IMG_HAS_BACKING_COMMAND =
+ "qemu-img info --output=json %s 2>/dev/null | grep -q '\"backing-filename\"'";
private String getVolumeUuidFromPath(String volumePath, PrimaryDataStoreTO volumePool) {
if (Storage.StoragePoolType.Linstor.equals(volumePool.getPoolType())) {
@@@ -279,19 -275,8 +280,20 @@@
return replaceBlockDeviceWithBackup(storagePoolMgr, volumePool, volumePath, backupPath, timeout, createTargetVolume, size);
}
+ // For NAS-backed incremental backups, the source qcow2 has a backing-file
+ // reference to its parent (set by nasbackup.sh's qemu-img rebase). A plain
+ // rsync would copy only the differential blocks, leaving a volume that
+ // depends on a backing file the primary storage doesn't have. Flatten the
+ // chain via qemu-img convert, which follows the backing-file links and
+ // produces a single self-contained qcow2.
+ if (hasBackingChain(backupPath)) {
- int flattenExit = Script.runSimpleBashScriptForExitValue(
- String.format(QEMU_IMG_FLATTEN_COMMAND, backupPath, volumePath), timeout, false);
++ String[] qemuImgCmd = new String[] { Script.getExecutableAbsolutePath("qemu-img"), "convert", "-O", "qcow2", backupPath, volumePath };
++ int flattenExit = Script.executeCommandForExitValue(qemuImgCmd);
+ return flattenExit == 0;
+ }
+
- int exitValue = Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, volumePath), timeout, false);
+ String[] rsyncCmd = new String[] { Script.getExecutableAbsolutePath("rsync"), "-az", backupPath, volumePath };
+ int exitValue = Script.executeCommandForExitValue(rsyncCmd);
return exitValue == 0;
}
diff --cc plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
index a0b1973fd8e,860674b329f..0f794f081a3
--- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
@@@ -24,32 -24,24 +24,33 @@@ import java.util.Map
import javax.inject.Inject;
import javax.naming.ConfigurationException;
-import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
-import org.apache.cloudstack.acl.Role;
-import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
-import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
-import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
-import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
-import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
-import org.apache.cloudstack.framework.config.ConfigKey;
-
import org.apache.cloudstack.acl.ControlledEntity;
++import org.apache.cloudstack.acl.Role;
+import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
+import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
-import org.apache.cloudstack.api.command.admin.user.RegisterUserKeyCmd;
+import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
+import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
+import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
+import org.apache.cloudstack.api.response.ApiKeyPairResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
+import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
+import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
import com.cloud.api.query.vo.ControlledViewEntity;
import com.cloud.configuration.ResourceLimit;
import com.cloud.configuration.dao.ResourceCountDao;
@@@ -611,25 -546,6 +612,28 @@@ public class MockAccountManager extend
}
@Override
+ public void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
+ }
+
+ @Override
+ public void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user) {
+ }
+
+ @Override
+ public Account getActiveAccountByUuid(String accountUuid) {
+ return null;
+ }
+
+ @Override
+ public User getOneActiveUserForAccount(Account account) {
+ return null;
+ }
+
+ @Override
+ public Account getAccountByUuid(String accountUuid) {
+ return null;
+ }
++
+ public void refreshRoleCheckersCacheOnPermissionsChange(Role role) {
+ }
}
diff --cc plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java
index 8327072639c,7ee02b511b3..a93b7a9e133
--- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java
+++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java
@@@ -48,7 -43,9 +44,8 @@@ import org.apache.cloudstack.api.respon
import org.apache.cloudstack.api.response.LdapUserResponse;
import org.apache.cloudstack.api.response.LinkAccountToLdapResponse;
import org.apache.cloudstack.api.response.LinkDomainToLdapResponse;
+ import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.messagebus.MessageBus;
-import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
import org.apache.cloudstack.ldap.dao.LdapConfigurationDao;
import org.apache.cloudstack.ldap.dao.LdapTrustMapDao;
import org.apache.commons.lang.Validate;
diff --cc plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java
index 2d0a2e2a741,60e1037f9ff..9c72e47e559
--- 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
@@@ -41,12 -42,14 +43,16 @@@ import org.apache.cloudstack.auth.UserO
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
- import org.apache.commons.lang3.ArrayUtils;
+ import org.apache.commons.lang.ArrayUtils;
+ import org.apache.commons.lang3.EnumUtils;
+ import org.apache.commons.lang3.ObjectUtils;
+import com.cloud.api.ApiDBUtils;
+ import com.cloud.api.ApiServer;
import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.domain.Domain;
import com.cloud.user.Account;
+ import com.cloud.utils.HttpUtils;
@APICommand(name = "listOauthProvider", description = "List OAuth providers registered", responseObject = OauthProviderResponse.class, entityType = {},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
@@@ -113,27 -135,9 +150,28 @@@ public class ListOAuthProvidersCmd exte
if (ArrayUtils.isNotEmpty(providerArray)) {
provider = providerArray[0];
}
+ boolean secretKeyAllowed = isSecretKeyAllowedForAuthenticatedCaller(params, session, remoteAddress, req);
- List<OauthProviderVO> resultList = _oauth2mgr.listOauthProviders(provider, id);
+ boolean domainRequested = ArrayUtils.isNotEmpty((String[])params.get(ApiConstants.DOMAIN_ID))
+ || ArrayUtils.isNotEmpty((String[])params.get(ApiConstants.DOMAIN));
+ domainId = _oauth2mgr.resolveDomainId(params);
+
+ if (domainRequested && domainId == null) {
+ ListResponse<OauthProviderResponse> response = new ListResponse<>();
+ response.setResponses(new ArrayList<>(), 0);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ return ApiResponseSerializer.toSerializedString(response, responseType);
+ }
+
+ List<OauthProviderVO> resultList = _oauth2mgr.listOauthProviders(provider, id, domainId);
+ boolean isAuthenticated = session != null && session.getAttribute(ApiConstants.USER_ID) != null;
+ if (domainRequested && domainId != null && domainId > 0) {
+ resultList.removeIf(p -> p.getDomainId() == null);
+ } else if (!domainRequested && !isAuthenticated) {
+ resultList.removeIf(p -> p.getDomainId() != null);
+ }
+
List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauth2mgr.listUserOAuth2AuthenticationProviders();
List<String> authenticatorPluginNames = new ArrayList<>();
for (UserOAuth2Authenticator authenticator : userOAuth2AuthenticatorPlugins) {
@@@ -142,11 -146,10 +180,12 @@@
}
List<OauthProviderResponse> 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.getAuthorizeUrl(), result.getTokenUrl(), domain);
+ result.getDescription(), result.getClientId(), secretKeyAllowed ? result.getSecretKey() : null,
- result.getRedirectUri());
- if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
++ result.getRedirectUri(), result.getAuthorizeUrl(), result.getTokenUrl(), domain);
+ boolean oauthEnabled = OAuth2AuthManager.isPluginEnabledForDomain(result.getDomainId());
+ if (oauthEnabled && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
r.setEnabled(true);
} else {
r.setEnabled(false);
diff --cc plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java
index 3b37f0f6e0b,e383b41999e..6b99432e4f6
--- 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
@@@ -60,26 -88,6 +91,31 @@@ public class GoogleOAuth2Provider exten
@Override
public boolean verifyUser(String email, String secretCode) {
+ return verifyUser(email, secretCode, null);
+ }
+
+ @Override
+ public String verifySecretCodeAndFetchEmail(String secretCode) {
+ return verifySecretCodeAndFetchEmail(secretCode, null);
+ }
+
++ @Override
++ public String verifySecretCodeAndFetchEmail(String secretCode, Long domainId) {
++ return verifyCodeAndFetchEmailInternal(secretCode, domainId, null);
++ }
++
+ protected void clearAccessAndRefreshTokens() {
+ accessToken = null;
+ refreshToken = null;
+ }
+
+ @Override
+ 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");
}
@@@ -89,21 -97,24 +125,24 @@@
throw new CloudAuthenticationException("Google provider is not registered, so user cannot be verified");
}
- String verifiedEmail = verifySecretCodeAndFetchEmail(secretCode, domainId);
+ String verifiedEmail = consumeValidatedEmailFromCache(secretCode);
+ if (StringUtils.isEmpty(verifiedEmail)) {
- verifiedEmail = verifyCodeAndFetchEmailInternal(secretCode, providerVO);
++ verifiedEmail = verifyCodeAndFetchEmailInternal(secretCode, domainId, providerVO);
+ }
- if (!email.equals(verifiedEmail)) {
+ 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 verifySecretCodeAndFetchEmail(String secretCode, Long domainId) {
- OauthProviderVO provider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
- String clientId = provider.getClientId();
- String secret = provider.getSecretKey();
- String redirectURI = provider.getRedirectUri();
- protected String verifyCodeAndFetchEmailInternal(String secretCode, OauthProviderVO googleProvider) {
++ protected String verifyCodeAndFetchEmailInternal(String secretCode, Long domainId, OauthProviderVO googleProvider) {
+ if (googleProvider == null) {
- googleProvider = _oauthProviderDao.findByProvider(getName());
++ googleProvider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
+ }
+ String clientId = googleProvider.getClientId();
+ String secret = googleProvider.getSecretKey();
+ String redirectURI = googleProvider.getRedirectUri();
GoogleClientSecrets clientSecrets = new GoogleClientSecrets()
.setWeb(new GoogleClientSecrets.Details()
.setClientId(clientId)
@@@ -146,7 -157,18 +185,8 @@@
} catch (IOException e) {
throw new CloudRuntimeException(String.format("Failed to fetch the email address with the provided secret: %s", e.getMessage()));
}
- return userinfo.getEmail();
+ String verifiedEmail = userinfo.getEmail();
+ addValidatedEmailToCache(secretCode, verifiedEmail);
+ return verifiedEmail;
}
-
- @Override
- public String verifyCodeAndFetchEmail(String secretCode) {
- return verifyCodeAndFetchEmailInternal(secretCode, null);
- }
--
- @Override
- public String getUserEmailAddress() throws CloudRuntimeException {
- return null;
- }
}
diff --cc plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java
index 1f4a4eb363c,a6c628182b4..a419cecc6b4
--- 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
@@@ -28,8 -33,6 +33,7 @@@ import org.junit.Before
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
- import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@@ -79,78 -83,95 +84,95 @@@ public class GoogleOAuth2ProviderTest
}
@Test(expected = CloudRuntimeException.class)
- public void testVerifyUserWithInvalidSecretCode() throws IOException {
- OauthProviderVO providerVO = mock(OauthProviderVO.class);
- when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO);
- when(providerVO.getProvider()).thenReturn("testProvider");
- when(providerVO.getSecretKey()).thenReturn("testSecret");
- when(providerVO.getClientId()).thenReturn("testClientid");
- _googleOAuth2Provider.accessToken = "testAccessToken";
- _googleOAuth2Provider.refreshToken = "testRefreshToken";
- Oauth2 oauth2 = mock(Oauth2.class);
- try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
- (mock, context) -> when(mock.build()).thenReturn(oauth2))) {
- Userinfo userinfo = mock(Userinfo.class);
- Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
- when(oauth2.userinfo()).thenReturn(userinfo1);
- Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
- when(userinfo1.get()).thenReturn(userinfoGet);
- when(userinfoGet.execute()).thenReturn(userinfo);
- when(userinfo.getEmail()).thenReturn(null);
-
- _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
- }
+ public void testVerifyUserWithInvalidSecretCode() {
- when(_oauthProviderDao.findByProvider(anyString())).thenReturn(mockProvider);
++ when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(mockProvider);
+ doReturn(null).when(_googleOAuth2Provider).verifyCodeAndFetchEmailInternal(
- "secretCode", mockProvider);
++ "secretCode", null, mockProvider);
+
+ _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
}
@Test(expected = CloudRuntimeException.class)
- public void testVerifyUserWithMismatchedEmail() throws IOException {
- OauthProviderVO providerVO = mock(OauthProviderVO.class);
- when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO);
- when(providerVO.getProvider()).thenReturn("testProvider");
- when(providerVO.getSecretKey()).thenReturn("testSecret");
- when(providerVO.getClientId()).thenReturn("testClientid");
- _googleOAuth2Provider.accessToken = "testAccessToken";
- _googleOAuth2Provider.refreshToken = "testRefreshToken";
- Oauth2 oauth2 = mock(Oauth2.class);
- try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
- (mock, context) -> when(mock.build()).thenReturn(oauth2))) {
- Userinfo userinfo = mock(Userinfo.class);
- Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
- when(oauth2.userinfo()).thenReturn(userinfo1);
- Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
- when(userinfo1.get()).thenReturn(userinfoGet);
- when(userinfoGet.execute()).thenReturn(userinfo);
- when(userinfo.getEmail()).thenReturn("otheremail@example.com");
-
- _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
+ public void testVerifyUserWithMismatchedEmail() {
- when(_oauthProviderDao.findByProvider(anyString())).thenReturn(mockProvider);
++ when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(mockProvider);
+ doReturn("otheremail@example.com").when(_googleOAuth2Provider).verifyCodeAndFetchEmailInternal(
- "secretCode", mockProvider);
++ "secretCode", null, mockProvider);
+
+ _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
+ }
+
+ @Test
+ public void testVerifyUserEmail() {
- when(_oauthProviderDao.findByProvider(anyString())).thenReturn(mockProvider);
++ when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(mockProvider);
+ doReturn("email@example.com").when(_googleOAuth2Provider).verifyCodeAndFetchEmailInternal(
- "secretCode", mockProvider);
++ "secretCode", null, mockProvider);
+
+ boolean result = _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
+
+ assertTrue(result);
+ }
+
+ @Test
+ public void testCacheInitializationAndCleanupResources() {
+ // Verifies that cache cleanup executor is properly initialized
+ GoogleOAuth2Provider provider = new GoogleOAuth2Provider();
+ assertNotNull("GoogleOAuth2Provider should initialize", provider);
+ }
+
+ @Test
+ public void testNoSensitiveDataInErrorMessages() {
+ // Verifies that error messages don't expose sensitive information
- when(_oauthProviderDao.findByProvider(anyString())).thenReturn(mockProvider);
++ when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(mockProvider);
+ String testSecret = "secret_key";
+ String testEmail = "email@example.com";
+
+ try {
+ _googleOAuth2Provider.verifyUser(testEmail, testSecret);
+ fail("Expected exception");
+ } catch (Exception e) {
+ String errorMsg = e.getMessage();
+ // Verify sensitive terms are not in error messages
+ assertFalse("Error should not contain secret", errorMsg.toLowerCase().contains(testSecret));
+ assertFalse("Error should not contain email", errorMsg.toLowerCase().contains(testEmail));
+ assertFalse("Error should not contain token", errorMsg.toLowerCase().contains("access_token"));
+ }
+ }
+
+ @Test
+ public void testVerifyUserErrorHandlingAndCleanup() {
+ // Tests that any authentication error properly cleans up
- when(_oauthProviderDao.findByProvider(anyString())).thenReturn(mockProvider);
++ when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(mockProvider);
+
+ doReturn("error@example.com").when(_googleOAuth2Provider).verifyCodeAndFetchEmailInternal(
- "bad_code", mockProvider);
++ "bad_code", null, mockProvider);
+
+ try {
+ _googleOAuth2Provider.verifyUser("expected@example.com", "bad_code");
+ fail("Should throw exception for email mismatch");
+ } catch (CloudRuntimeException e) {
+ assertEquals("Should have proper error message",
+ "Unable to verify the email address with the provided secret",
+ e.getMessage());
}
}
@Test
- public void testVerifyUserEmail() throws IOException {
- OauthProviderVO providerVO = mock(OauthProviderVO.class);
- when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO);
- when(providerVO.getProvider()).thenReturn("testProvider");
- when(providerVO.getSecretKey()).thenReturn("testSecret");
- when(providerVO.getClientId()).thenReturn("testClientid");
- _googleOAuth2Provider.accessToken = "testAccessToken";
- _googleOAuth2Provider.refreshToken = "testRefreshToken";
- Oauth2 oauth2 = mock(Oauth2.class);
- try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
- (mock, context) -> when(mock.build()).thenReturn(oauth2))) {
- Userinfo userinfo = mock(Userinfo.class);
- Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
- when(oauth2.userinfo()).thenReturn(userinfo1);
- Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
- when(userinfo1.get()).thenReturn(userinfoGet);
- when(userinfoGet.execute()).thenReturn(userinfo);
- when(userinfo.getEmail()).thenReturn("email@example.com");
-
- boolean result = _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
-
- assertTrue(result);
- assertNull(_googleOAuth2Provider.accessToken);
- assertNull(_googleOAuth2Provider.refreshToken);
+ public void testMultipleFailedVerificationAttempts() {
+ // Tests that multiple failures are handled gracefully without cache pollution
- when(_oauthProviderDao.findByProvider(anyString())).thenReturn(mockProvider);
++ when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(mockProvider);
+
+ // Multiple failed attempts
+ for (int i = 0; i < 5; i++) {
+ try {
+ doReturn("wrong@example.com").when(_googleOAuth2Provider)
- .verifyCodeAndFetchEmailInternal("code_" + i, mockProvider);
++ .verifyCodeAndFetchEmailInternal("code_" + i, null, mockProvider);
+ _googleOAuth2Provider.verifyUser("correct@example.com", "code_" + i);
+ fail("Should fail on attempt " + i);
+ } catch (CloudRuntimeException e) {
+ // Expected - cache should be cleaned for each failure
+ assertTrue("Should report email verification failure",
+ e.getMessage().contains("email"));
+ }
}
}
}
diff --cc scripts/vm/hypervisor/kvm/nasbackup.sh
index 5b3bed7c875,656ff5ac28d..216f5b5a4de
--- a/scripts/vm/hypervisor/kvm/nasbackup.sh
+++ b/scripts/vm/hypervisor/kvm/nasbackup.sh
@@@ -287,51 -194,21 +310,59 @@@ print(len(files)
sleep 5
done
- # Use qemu-img convert to sparsify linstor backups which get bloated due to virsh backup-begin.
+ # Sparsify behavior:
+ # - For LINSTOR backups (existing): qemu-img convert sparsifies the bloated output.
+ # - For INCREMENTAL: rebase the resulting thin qcow2 onto its parent so the chain is self-describing
+ # (so a future restore can flatten without external chain metadata).
name="root"
+ # PARENT_PATHS arrives as a comma-separated list, one entry per VM volume in the same
+ # order as DISK_PATHS. Split into a bash array so we can index by disk position.
+ local -a parent_paths_arr=()
+ if [[ "$effective_mode" == "incremental" && -n "$PARENT_PATHS" ]]; then
+ IFS=',' read -ra parent_paths_arr <<< "$PARENT_PATHS"
+ fi
+ local disk_idx=0
while read -r disk fullpath; do
+ if [[ "$effective_mode" == "incremental" ]]; then
+ volUuid="${fullpath##*/}"
+ # Pick this disk's specific parent file. Each volume's backup is named after its
+ # own UUID, so a single PARENT_PATH would wrongly rebase data disks onto the root
+ # parent.
+ if [[ $disk_idx -ge ${#parent_paths_arr[@]} ]]; then
+ echo "PARENT_PATHS list shorter than DISK_PATHS — missing parent for disk index $disk_idx"
+ cleanup
+ exit 1
+ fi
+ local this_parent_rel="${parent_paths_arr[$disk_idx]}"
+ local parent_abs="$mount_point/$this_parent_rel"
+ if [[ ! -f "$parent_abs" ]]; then
+ echo "Parent backup file does not exist on NAS: $parent_abs"
+ cleanup
+ exit 1
+ fi
+ local parent_rel
+ parent_rel=$(realpath --relative-to="$dest" "$parent_abs")
+ if ! qemu-img rebase -u -b "$parent_rel" -F qcow2 "$dest/$name.$volUuid.qcow2" >> "$logFile" 2> >(cat >&2); then
+ echo "qemu-img rebase failed for $dest/$name.$volUuid.qcow2 onto $parent_rel"
+ cleanup
+ exit 1
+ fi
+ name="datadisk"
+ disk_idx=$((disk_idx + 1))
+ continue
+ fi
- if [[ "$fullpath" != /dev/drbd/by-res/* ]]; then
+ if [[ "$fullpath" == /dev/drbd/by-res/* ]]; then
+ volUuid=$(get_linstor_uuid_from_path "$fullpath")
+ elif [[ "$fullpath" == /dev/drbd[0-9]* ]]; then
+ if ! volUuid=$(get_linstor_uuid_from_device "$fullpath"); then
+ echo "Failed to resolve LINSTOR volume UUID for $fullpath"
+ cleanup
+ exit 1
+ fi
+ else
+ name="datadisk"
continue
fi
- volUuid=$(get_linstor_uuid_from_path "$fullpath")
if ! qemu-img convert -O qcow2 "$dest/$name.$volUuid.qcow2" "$dest/$name.$volUuid.qcow2.tmp" >> "$logFile" 2> >(cat >&2); then
echo "qemu-img convert failed for $dest/$name.$volUuid.qcow2"
cleanup
diff --cc server/src/main/java/com/cloud/user/AccountManager.java
index e3840e29725,f12a1b68b03..054880d45b4
--- a/server/src/main/java/com/cloud/user/AccountManager.java
+++ b/server/src/main/java/com/cloud/user/AccountManager.java
@@@ -20,9 -20,8 +20,10 @@@ import java.net.InetAddress
import java.util.List;
import java.util.Map;
+import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.acl.ControlledEntity;
+ import org.apache.cloudstack.acl.Role;
+import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
@@@ -211,7 -209,5 +212,9 @@@ public interface AccountManager extend
void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount);
+ void verifyCallerPrivilegeForUserOrAccountOperations(User user);
+
+ void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user);
++
+ void refreshRoleCheckersCacheOnPermissionsChange(Role role);
}
diff --cc server/src/main/java/com/cloud/user/AccountManagerImpl.java
index e4d835f74b1,b4c23beaef2..7c1d1e9af8b
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@@ -45,17 -42,8 +45,19 @@@ import javax.crypto.spec.SecretKeySpec
import javax.inject.Inject;
import javax.naming.ConfigurationException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.projects.dao.ProjectInvitationDao;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.user.dao.SSHKeyPairDao;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.db.TransactionCallbackWithException;
++
+ import org.apache.cloudstack.acl.APIAclChecker;
import org.apache.cloudstack.acl.APIChecker;
+import org.apache.cloudstack.acl.ApiKeyPairManagerImpl;
+import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
+import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.InfrastructureEntity;
import org.apache.cloudstack.acl.QuerySelector;
@@@ -1722,9 -1600,16 +1736,17 @@@ public class AccountManagerImpl extend
}
}
+ @Override
+ public void refreshRoleCheckersCacheOnPermissionsChange(Role role) {
+ List<APIAclChecker> aclCheckers = getApiACLCheckers();
+ for (final APIAclChecker aclChecker : aclCheckers) {
+ aclChecker.refreshRoleCacheOnPermissionsChange(role);
+ }
+ }
+
- protected void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
- logger.debug(String.format("Verifying whether the caller has the correct privileges based on the user's role type and API permissions: %s", user));
+ @Override
+ public void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
+ logger.debug("Verifying whether the caller has the correct privileges based on the user's role type and API permissions: {}", user);
Account userAccount = getAccount(user.getAccountId());
if (!Account.Type.PROJECT.equals(userAccount.getType())) {
diff --cc server/src/test/java/com/cloud/template/TemplateManagerImplTest.java
index 6d8003914d3,af5bdbf9fd1..8c7994793e4
--- a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java
+++ b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java
@@@ -19,70 -19,28 +19,74 @@@
package com.cloud.template;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import com.cloud.agent.AgentManager;
+import com.cloud.api.query.dao.SnapshotJoinDao;
+import com.cloud.api.query.dao.UserVmJoinDao;
+import com.cloud.api.query.vo.UserVmJoinVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.event.dao.UsageEventDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.host.Status;
+import com.cloud.host.DetailVO;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.host.dao.HostDetailsDao;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.HypervisorGuruManager;
+import com.cloud.projects.ProjectManager;
+import com.cloud.resourcelimit.CheckedReservation;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.GuestOSVO;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.StoragePoolStatus;
+import com.cloud.storage.TemplateProfile;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.storage.dao.LaunchPermissionDao;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.StoragePoolHostDao;
+import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VMTemplateDetailsDao;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VMTemplateZoneDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.user.ResourceLimitService;
+import com.cloud.user.User;
+import com.cloud.user.UserData;
++import com.cloud.user.UserDataVO;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.AccountDao;
++import com.cloud.user.dao.UserDataDao;
++import com.cloud.utils.UriUtils;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.uservm.UserVm;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachine.State;
+import com.cloud.vm.VmIsoMapVO;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.dao.VmIsoMapDao;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
+import junit.framework.TestCase;
+ import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd;
import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd;
import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd;
import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd;
@@@ -233,21 -223,12 +238,27 @@@ public class TemplateManagerImplTest ex
@Mock
HeuristicRuleHelper heuristicRuleHelperMock;
+ @Mock
+ UserVmDao _userVmDao;
+
+ @Mock
+ VmIsoMapDao _vmIsoMapDao;
+
+ @Mock
+ HostDao _hostDao;
+
+ @Mock
+ HostDetailsDao _hostDetailsDao;
+
+ @Mock
+ UserVmJoinDao _userVmJoinDao;
+
+ @Mock
+ private UserDataDao userDataDaoMock;
+
+ @Mock
+ private UserDataVO userDataMock;
+
public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
AtomicInteger ai = new AtomicInteger(0);
public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
diff --cc server/src/test/java/com/cloud/user/AccountManagerImplTest.java
index 379ae76d119,119704cc397..955c939f4fa
--- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
+++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
@@@ -19,15 -19,12 +19,15 @@@ package com.cloud.user
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
+import com.cloud.utils.Ternary;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.Collections;
- import java.util.HashMap;
+import java.util.Date;
++import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@@ -2032,115 -1617,118 +2063,230 @@@ public class AccountManagerImplTest ext
accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock);
}
+ @Test(expected = InvalidParameterValueException.class)
+ public void testPasswordChangeRequiredWithSamlThrowsException() {
+ accountManagerImpl.createUser(
+ "user", "pass", "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.SAML2, true
+ );
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void testPasswordChangeRequiredWithLdapSourceThrows() {
+ accountManagerImpl.createUser(
+ "user", "pass", "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.LDAP, true);
+ }
+
+ @Test(expected = CloudRuntimeException.class)
+ public void testDomainNotFound() {
+ Mockito.when(_domainMgr.getDomain(1L)).thenReturn(null);
+ accountManagerImpl.createUser(
+ "user", "pass", "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.UNKNOWN, false);
+ }
+
+ @Test(expected = CloudRuntimeException.class)
+ public void testCreateUserInactiveDomain() {
+ Mockito.when(domainVoMock.getState()).thenReturn(Domain.State.Inactive);
+ Mockito.when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domainVoMock);
+ accountManagerImpl.createUser(
+ "user", "pass", "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.NATIVE, false);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void testCreateUserCheckAccess() {
+ Mockito.when(domainVoMock.getState()).thenReturn(Domain.State.Active);
+ Mockito.when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domainVoMock);
+ Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
+ accountManagerImpl.createUser(
+ "user", "pass", "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.NATIVE, false);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void testCreateUserMissingOrProjectAccount() {
+ Mockito.when(domainVoMock.getState()).thenReturn(Domain.State.Active);
+ Mockito.when(_accountDao.findEnabledAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(accountMock);
+ Mockito.when(accountMock.getType()).thenReturn(Account.Type.PROJECT);
+ Mockito.when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domainVoMock);
+ Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
+ accountManagerImpl.createUser(
+ "user", "pass", "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.NATIVE, false);
+ }
+
+ @Test
+ public void testCreateUserSuccess() {
+ Account rootAdminAccount = Mockito.mock(Account.class);
+ Mockito.when(rootAdminAccount.getId()).thenReturn(1L);
+ Mockito.when(accountManagerImpl.isRootAdmin(1L)).thenReturn(true);
+ User callingUser = Mockito.mock(User.class);
+ CallContext.register(callingUser, rootAdminAccount);
+
+ String newPassword = "newPassword";
+ configureUserMockAuthenticators(newPassword);
+ Mockito.doNothing().when(accountManagerImpl).checkAccess(any(Account.class), any(Domain.class));
+ Mockito.doReturn(accountMock).when(accountManagerImpl).getAccount(Mockito.anyLong());
+ Mockito.doNothing().when(passwordPolicyMock).verifyIfPasswordCompliesWithPasswordPolicies(Mockito.anyString(), Mockito.anyString(), Mockito.anyLong());
+ Mockito.when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domainVoMock);
+ Mockito.when(domainVoMock.getState()).thenReturn(Domain.State.Active);
+
+ Mockito.when(_accountDao.findEnabledAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(accountMock);
+ Mockito.when(accountMock.getId()).thenReturn(10L);
+ Mockito.when(accountMock.getType()).thenReturn(Account.Type.NORMAL);
+
+ Mockito.when(userAccountDao.validateUsernameInDomain(Mockito.anyString(), Mockito.anyLong())).thenReturn(true);
+ Mockito.when(userDaoMock.findUsersByName(Mockito.anyString())).thenReturn(Collections.emptyList());
+ UserVO createdUser = new UserVO();
+ String userMockUUID = "userMockUUID";
+ createdUser.setUuid(userMockUUID);
+ Mockito.when(userDaoMock.persist(Mockito.any(UserVO.class))).thenReturn(createdUser);
+ UserVO userResultVO = accountManagerImpl.createUser(
+ "user", newPassword, "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.NATIVE, false
+ );
+ Assert.assertNotNull(userResultVO);
+ UserVO userResultPasswordChangeVO = accountManagerImpl.createUser(
+ "user", newPassword, "fn", "ln", "e@mail.com",
+ "UTC", "acct", 1L, null,
+ User.Source.NATIVE, true
+ );
+ Assert.assertNotNull(userResultVO);
+ }
+
+ @Test
+ public void deleteAndCleanupUserTestUserCleanup() {
+ long userId = userVoMock.getId();
+ Mockito.doNothing().when(accountManagerImpl).removeUserApiKeys(userId);
+ Mockito.doReturn(true).when(_projectMgr).cleanupProjectsForUser(null, userVoMock);
+
+ accountManagerImpl.deleteAndCleanupUser(userVoMock);
+
+ Mockito.verify(accountManagerImpl).removeUserApiKeys(userId);
+ Mockito.verify(_projectMgr).cleanupProjectsForUser(null, userVoMock);
+ }
++
+ // --- Tests for checkRoleEscalation ---
+
+ private void setPrivateField(Object target, String fieldName, Object value) throws Exception {
+ Class<?> clazz = target.getClass();
+ while (clazz != null) {
+ try {
+ java.lang.reflect.Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(target, value);
+ return;
+ } catch (NoSuchFieldException e) {
+ clazz = clazz.getSuperclass();
+ }
+ }
+ throw new NoSuchFieldException(fieldName);
+ }
+
+ @Test
+ public void testCheckRoleEscalationSamePermissionsShouldPass() throws Exception {
+ APIChecker checker = Mockito.mock(APIChecker.class);
+ List<String> apis = Arrays.asList("api1", "api2", "api3");
+ Mockito.when(checker.isEnabled()).thenReturn(true);
+
+ Account caller = Mockito.mock(Account.class);
+ Account requested = Mockito.mock(Account.class);
+
+ accountManagerImpl.setApiAccessCheckers(Arrays.asList(checker));
+ setPrivateField(accountManagerImpl, "apiNameList", new ArrayList<>(apis));
+
+ accountManagerImpl.checkRoleEscalation(caller, requested);
+ }
+
+ @Test
+ public void testCheckRoleEscalationCallerHasMorePermissionsShouldPass() throws Exception {
+ List<String> allApis = Arrays.asList("api1", "api2", "api3");
+ List<String> requestedApis = Arrays.asList("api1", "api2");
+
+ APIAclChecker checker = Mockito.mock(APIAclChecker.class);
+ Mockito.when(checker.isEnabled()).thenReturn(true);
+
+ Account caller = Mockito.mock(Account.class);
+ Account requested = Mockito.mock(Account.class);
+
+ Mockito.when(checker.getApisAllowedToAccount(Mockito.eq(requested), Mockito.anyList())).thenReturn(requestedApis);
+ Mockito.when(checker.getApisAllowedToAccount(Mockito.eq(caller), Mockito.anyList())).thenReturn(allApis);
+
+ accountManagerImpl.setApiAccessCheckers(Arrays.asList(checker));
+ setPrivateField(accountManagerImpl, "apiNameList", new ArrayList<>(allApis));
+
+ accountManagerImpl.checkRoleEscalation(caller, requested);
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void testCheckRoleEscalationRequestedHasMorePermissionsShouldThrow() throws Exception {
+ List<String> allApis = Arrays.asList("api1", "api2", "api3");
+ List<String> requestedApis = Arrays.asList("api1", "api2", "api3");
+ List<String> callerApis = Arrays.asList("api1");
+
+ APIAclChecker checker = Mockito.mock(APIAclChecker.class);
+ Mockito.when(checker.isEnabled()).thenReturn(true);
+
+ Account caller = Mockito.mock(Account.class);
+ Account requested = Mockito.mock(Account.class);
+
+ Mockito.when(checker.getApisAllowedToAccount(Mockito.eq(requested), Mockito.anyList())).thenReturn(requestedApis);
+ Mockito.when(checker.getApisAllowedToAccount(Mockito.eq(caller), Mockito.anyList())).thenReturn(callerApis);
+
+ accountManagerImpl.setApiAccessCheckers(Arrays.asList(checker));
+ setPrivateField(accountManagerImpl, "apiNameList", new ArrayList<>(allApis));
+
+ accountManagerImpl.checkRoleEscalation(caller, requested);
+ }
+
+ @Test
+ public void testCheckRoleEscalationEmptyApiListShouldPass() throws Exception {
+ APIAclChecker checker = Mockito.mock(APIAclChecker.class);
+ Mockito.when(checker.isEnabled()).thenReturn(true);
+ Mockito.when(checker.getApisAllowedToAccount(Mockito.any(Account.class), Mockito.anyList())).thenReturn(Collections.emptyList());
+
+ Account caller = Mockito.mock(Account.class);
+ Account requested = Mockito.mock(Account.class);
+
+ accountManagerImpl.setApiAccessCheckers(Arrays.asList(checker));
+ setPrivateField(accountManagerImpl, "apiNameList", new ArrayList<>());
+
+ accountManagerImpl.checkRoleEscalation(caller, requested);
+ }
+
+ @Test
+ public void testCheckRoleEscalationMultipleCheckersAppliedSequentially() throws Exception {
+ List<String> allApis = Arrays.asList("api1", "api2", "api3");
+ List<String> afterChecker1 = Arrays.asList("api1", "api2");
+ List<String> afterChecker2 = Arrays.asList("api1");
+
+ APIAclChecker checker1 = Mockito.mock(APIAclChecker.class);
+ Mockito.when(checker1.isEnabled()).thenReturn(true);
+ APIAclChecker checker2 = Mockito.mock(APIAclChecker.class);
+ Mockito.when(checker2.isEnabled()).thenReturn(true);
+
+ Account caller = Mockito.mock(Account.class);
+ Account requested = Mockito.mock(Account.class);
+
+ // requested: checker1 filters to [api1, api2], checker2 further filters to [api1]
+ Mockito.when(checker1.getApisAllowedToAccount(Mockito.eq(requested), Mockito.eq(allApis))).thenReturn(afterChecker1);
+ Mockito.when(checker2.getApisAllowedToAccount(Mockito.eq(requested), Mockito.eq(afterChecker1))).thenReturn(afterChecker2);
+ // caller: same filtering, so no escalation
+ Mockito.when(checker1.getApisAllowedToAccount(Mockito.eq(caller), Mockito.eq(afterChecker2))).thenReturn(afterChecker2);
+ Mockito.when(checker2.getApisAllowedToAccount(Mockito.eq(caller), Mockito.eq(afterChecker2))).thenReturn(afterChecker2);
+
+ accountManagerImpl.setApiAccessCheckers(Arrays.asList(checker1, checker2));
+ setPrivateField(accountManagerImpl, "apiNameList", new ArrayList<>(allApis));
+
+ accountManagerImpl.checkRoleEscalation(caller, requested);
+ }
}
diff --cc ui/src/config/section/compute.js
index b6e83b5eb43,10bf0a3973a..598c80bc480
--- a/ui/src/config/section/compute.js
+++ b/ui/src/config/section/compute.js
@@@ -18,19 -18,8 +18,20 @@@
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
import { isZoneCreated } from '@/utils/zone'
+import { getAPI, postAPI, getBaseUrl } from '@/api'
+import { getLatestKubernetesIsoParams } from '@/utils/acsrepo'
+import kubernetesIcon from '@/assets/icons/kubernetes.svg?inline'
+ import { escapeHtml } from '@/utils/util'
+const attachedIsoCount = (record) => (record.isos && record.isos.length) || (record.isoid ? 1 : 0)
+// Server pre-computes the effective cap (cluster-scoped vm.iso.max.count clamped to the
+// hypervisor's own limit). Fall back to the hypervisor floor for older servers.
+const isoMaxCount = (record) => record.isomaxcount != null
+ ? record.isomaxcount
+ : (record.hypervisor === 'KVM' ? 2 : 1)
+const isoActionAvailable = (record) =>
+ record.hypervisor !== 'External' && ['Running', 'Stopped'].includes(record.state) && record.vmtype !== 'sharedfsvm'
+
export default {
name: 'compute',
title: 'label.compute',
diff --cc ui/src/config/section/network.js
index 30608a82a98,f59f6e6d15b..3be69ab1061
--- a/ui/src/config/section/network.js
+++ b/ui/src/config/section/network.js
@@@ -18,9 -18,9 +18,10 @@@
import { shallowRef, defineAsyncComponent } from 'vue'
import store from '@/store'
import tungsten from '@/assets/icons/tungsten.svg?inline'
-import { isAdmin } from '@/role'
+import { isAdmin, isAdminOrDomainAdmin } from '@/role'
import { isZoneCreated } from '@/utils/zone'
+import { vueProps } from '@/vue-app'
+ import { escapeHtml } from '@/utils/util'
export default {
name: 'network',