This is an automated email from the ASF dual-hosted git repository. rohit pushed a commit to branch 4.15 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.15 by this push: new 99a9063 server: Added recursive fetch of child domains for listUsageRecords API call (#4717) 99a9063 is described below commit 99a9063cf40e12c585ad61699aa931280f2ec4c0 Author: Spaceman1984 <49917670+spaceman1...@users.noreply.github.com> AuthorDate: Sat Apr 10 09:45:29 2021 +0200 server: Added recursive fetch of child domains for listUsageRecords API call (#4717) When calling the listUasageRecords API records per domain are fetched recursively. This is not the case if you specify a domain id. This PR adds a new parameter to enable fetching records recursively (isRecursive) when passing the domain id. --- .../command/admin/usage/ListUsageRecordsCmd.java | 9 ++ .../java/com/cloud/usage/UsageServiceImpl.java | 105 +++++++++++++++++---- 2 files changed, 94 insertions(+), 20 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageRecordsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageRecordsCmd.java index 748b9d7..91a38b0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageRecordsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageRecordsCmd.java @@ -85,6 +85,11 @@ public class ListUsageRecordsCmd extends BaseListCmd { @Parameter(name = ApiConstants.OLD_FORMAT, type = CommandType.BOOLEAN, description = "Flag to enable description rendered in old format which uses internal database IDs instead of UUIDs. False by default.") private Boolean oldFormat; + @Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, + description = "Specify if usage records should be fetched recursively per domain. If an account id is passed, records will be limited to that account.", + since = "4.15") + private Boolean recursive = false; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -153,6 +158,10 @@ public class ListUsageRecordsCmd extends BaseListCmd { return oldFormat != null && oldFormat; } + public Boolean isRecursive() { + return recursive; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java index 2d8d937..253daad 100644 --- a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java +++ b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java @@ -26,6 +26,7 @@ import java.util.TimeZone; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.domain.Domain; import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.ListUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; @@ -200,22 +201,41 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag } } - boolean isAdmin = false; - boolean isDomainAdmin = false; + boolean ignoreAccountId = false; + boolean isDomainAdmin = _accountService.isDomainAdmin(caller.getId()); + boolean isNormalUser = _accountService.isNormalUser(caller.getId()); //If accountId couldn't be found using accountName and domainId, get it from userContext if (accountId == null) { accountId = caller.getId(); //List records for all the accounts if the caller account is of type admin. //If account_id or account_name is explicitly mentioned, list records for the specified account only even if the caller is of type admin - if (_accountService.isRootAdmin(caller.getId())) { - isAdmin = true; - } else if (_accountService.isDomainAdmin(caller.getId())) { - isDomainAdmin = true; - } + ignoreAccountId = _accountService.isRootAdmin(caller.getId()); s_logger.debug("Account details not available. Using userContext accountId: " + accountId); } + // Check if a domain admin is allowed to access the requested domain id + if (isDomainAdmin) { + if (domainId != null) { + Account callerAccount = _accountService.getAccount(caller.getId()); + Domain domain = _domainDao.findById(domainId); + _accountService.checkAccess(callerAccount, domain); + } else { + // Domain admins can only access their own domain's usage records. + // Set the domain if not specified. + domainId = caller.getDomainId(); + } + + if (cmd.getAccountId() != null) { + // Check if a domain admin is allowed to access the requested account info. + checkDomainAdminAccountAccess(accountId, domainId); + } + } + + // By default users do not have access to this API. + // Adding checks here in case someone changes the default access. + checkUserAccess(cmd, accountId, caller, isNormalUser); + Date startDate = cmd.getStartDate(); Date endDate = cmd.getEndDate(); if (startDate.after(endDate)) { @@ -234,22 +254,27 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag SearchCriteria<UsageVO> sc = _usageDao.createSearchCriteria(); - if (accountId != -1 && accountId != Account.ACCOUNT_ID_SYSTEM && !isAdmin && !isDomainAdmin) { - sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); - } - - if (isDomainAdmin) { - SearchCriteria<DomainVO> sdc = _domainDao.createSearchCriteria(); - sdc.addOr("path", SearchCriteria.Op.LIKE, _domainDao.findById(caller.getDomainId()).getPath() + "%"); - List<DomainVO> domains = _domainDao.search(sdc, null); - List<Long> domainIds = new ArrayList<Long>(); - for (DomainVO domain : domains) - domainIds.add(domain.getId()); - sc.addAnd("domainId", SearchCriteria.Op.IN, domainIds.toArray()); + if (accountId != -1 && accountId != Account.ACCOUNT_ID_SYSTEM && !ignoreAccountId) { + // account exists and either domain on user role + // If not recursive and the account belongs to the user/domain admin, or the account was passed in, filter + if ((accountId == caller.getId() && !cmd.isRecursive()) || cmd.getAccountId() != null){ + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + } } if (domainId != null) { - sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + if (cmd.isRecursive()) { + SearchCriteria<DomainVO> sdc = _domainDao.createSearchCriteria(); + sdc.addOr("path", SearchCriteria.Op.LIKE, _domainDao.findById(domainId).getPath() + "%"); + List<DomainVO> domains = _domainDao.search(sdc, null); + List<Long> domainIds = new ArrayList<Long>(); + for (DomainVO domain : domains) { + domainIds.add(domain.getId()); + } + sc.addAnd("domainId", SearchCriteria.Op.IN, domainIds.toArray()); + } else { + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + } } if (usageType != null) { @@ -372,6 +397,46 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag return new Pair<List<? extends Usage>, Integer>(usageRecords.first(), usageRecords.second()); } + private void checkUserAccess(ListUsageRecordsCmd cmd, Long accountId, Account caller, boolean isNormalUser) { + if (isNormalUser) { + // A user can only access their own account records + if (caller.getId() != accountId) { + throw new PermissionDeniedException("Users are only allowed to list usage records for their own account."); + } + // Users cannot get recursive records + if (cmd.isRecursive()) { + throw new PermissionDeniedException("Users are not allowed to list usage records recursively."); + } + // Users cannot get domain records + if (cmd.getDomainId() != null) { + throw new PermissionDeniedException("Users are not allowed to list usage records for a domain"); + } + } + } + + private void checkDomainAdminAccountAccess(Long accountId, Long domainId) { + Account account = _accountService.getAccount(accountId); + boolean matchFound = false; + + if (account.getDomainId() == domainId) { + matchFound = true; + } else { + + // Check if the account is in a child domain of this domain admin. + List<DomainVO> childDomains = _domainDao.findAllChildren(_domainDao.findById(domainId).getPath(), domainId); + + for (DomainVO domainVO : childDomains) { + if (account.getDomainId() == domainVO.getId()) { + matchFound = true; + break; + } + } + } + if (!matchFound) { + throw new PermissionDeniedException("Domain admins may only retrieve usage records for accounts in their own domain and child domains."); + } + } + @Override public TimeZone getUsageTimezone() { return _usageTimezone;