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;

Reply via email to