This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.20 by this push:
     new d17de834a57 Disable API Key Access for users, accounts and domains 
(#9741)
d17de834a57 is described below

commit d17de834a57237b79f24850f97190bfc378f31a9
Author: Abhisar Sinha <[email protected]>
AuthorDate: Tue Dec 3 12:10:54 2024 +0530

    Disable API Key Access for users, accounts and domains (#9741)
    
    * cli changes to update user/account, list by apikeyaccess, domain level 
setting
    
    * UI changes for updating user/account and searchfilter in listview
    
    * make the api parameters and setting accessible only to root admin
    
    * revert changes to ui/package-lock.json
    
    * minor changes to description strings
    
    * UT for ApiServer and AccountManagerImpl classes
    
    * fix pre-commit failure
    
    * Added a constant for the string System
    
    * UT for searchForUsers and searchForAccounts
    
    * Fix marvin test error
    
    * Update schema to use idempotent add column
    
    * Fix `updateTemplatePermission` when the UI is set to a language other 
than English (#9766)
    
    * Fix updateTemplatePermission UI in non-english language
    
    * Improve fix
    
    ---------
    
    Co-authored-by: Lucas Martins <[email protected]>
    
    * Added user name uuid to logging
    
    * Add events when api key access is changed via api or config setting
    
    * fix the userid for api key access update event
    
    * Fix ut failure after event logging
    
    * Convert drop down to radio-button in edit user and account
    
    * Add ApiKeyAccess status in User InfoCard for Users if Api key is generated
    
    * Return apiKeyAccess in user and account response only for Root Admin
    
    * fixed noredist build failure
    
    * Show apikeyaccess on the left panel in the user view for root admins as 
well
    
    * don't show divider if apiKeyAccess is not shown to user
    
    * Fix events generated to set Username, Account and Domain of the caller 
correctly
    
    * cli changes to update user/account, list by apikeyaccess, domain level 
setting
    
    * UI changes for updating user/account and searchfilter in listview
    
    * make the api parameters and setting accessible only to root admin
    
    * revert changes to ui/package-lock.json
    
    * minor changes to description strings
    
    * UT for ApiServer and AccountManagerImpl classes
    
    * fix pre-commit failure
    
    * Added a constant for the string System
    
    * UT for searchForUsers and searchForAccounts
    
    * Fix marvin test error
    
    * Update schema to use idempotent add column
    
    * Added user name uuid to logging
    
    * Add events when api key access is changed via api or config setting
    
    * fix the userid for api key access update event
    
    * Fix ut failure after event logging
    
    * Convert drop down to radio-button in edit user and account
    
    * Add ApiKeyAccess status in User InfoCard for Users if Api key is generated
    
    * Return apiKeyAccess in user and account response only for Root Admin
    
    * fixed noredist build failure
    
    * Show apikeyaccess on the left panel in the user view for root admins as 
well
    
    * don't show divider if apiKeyAccess is not shown to user
    
    * Fix events generated to set Username, Account and Domain of the caller 
correctly
    
    * Added DB upgrade path from 42000 to 42010
    
    ---------
    
    Co-authored-by: Daan Hoogland <[email protected]>
    Co-authored-by: Lucas Martins 
<[email protected]>
    Co-authored-by: Lucas Martins <[email protected]>
---
 api/src/main/java/com/cloud/event/EventTypes.java  |   1 +
 api/src/main/java/com/cloud/user/Account.java      |   4 +
 .../main/java/com/cloud/user/AccountService.java   |   5 +-
 api/src/main/java/com/cloud/user/User.java         |   5 +
 .../org/apache/cloudstack/api/ApiConstants.java    |  27 +++
 .../command/admin/account/UpdateAccountCmd.java    |  15 +-
 .../api/command/admin/user/GetUserKeysCmd.java     |   9 +-
 .../api/command/admin/user/ListUsersCmd.java       |  16 +-
 .../api/command/admin/user/UpdateUserCmd.java      |   8 +
 .../api/command/user/account/ListAccountsCmd.java  |   8 +
 .../cloudstack/api/response/AccountResponse.java   |   8 +
 .../cloudstack/api/response/RegisterResponse.java  |  13 +-
 .../cloudstack/api/response/UserResponse.java      |   8 +
 .../org/apache/cloudstack/query/QueryService.java  |   3 +-
 .../com/cloud/upgrade/DatabaseUpgradeChecker.java  |   2 +
 .../com/cloud/upgrade/dao/Upgrade42000to42010.java |  83 +++++++++
 .../src/main/java/com/cloud/user/AccountVO.java    |  13 ++
 .../src/main/java/com/cloud/user/UserVO.java       |  12 ++
 .../java/com/cloud/user/dao/AccountDaoImpl.java    |  30 +++-
 .../META-INF/db/schema-42000to42010-cleanup.sql    |  20 +++
 .../resources/META-INF/db/schema-42000to42010.sql  |  24 +++
 .../META-INF/db/views/cloud.account_view.sql       |   1 +
 .../META-INF/db/views/cloud.user_view.sql          |   1 +
 .../cloudstack/framework/config/ConfigKey.java     |   1 +
 .../contrail/management/MockAccountManager.java    |   4 +-
 server/src/main/java/com/cloud/api/ApiDBUtils.java |   6 +-
 server/src/main/java/com/cloud/api/ApiServer.java  |  33 ++++
 .../java/com/cloud/api/query/QueryManagerImpl.java |  43 ++++-
 .../com/cloud/api/query/ViewResponseHelper.java    |   6 +-
 .../cloud/api/query/dao/AccountJoinDaoImpl.java    |   3 +
 .../cloud/api/query/dao/UserAccountJoinDao.java    |   3 +-
 .../api/query/dao/UserAccountJoinDaoImpl.java      |   6 +-
 .../java/com/cloud/api/query/vo/AccountJoinVO.java |   7 +
 .../com/cloud/api/query/vo/UserAccountJoinVO.java  |   7 +
 .../configuration/ConfigurationManagerImpl.java    |  19 ++-
 .../java/com/cloud/user/AccountManagerImpl.java    |  60 ++++++-
 .../src/test/java/com/cloud/api/ApiServerTest.java |  29 ++++
 .../com/cloud/api/query/QueryManagerImplTest.java  |  94 ++++++++++
 .../com/cloud/user/AccountManagerImplTest.java     |  45 +++++
 .../com/cloud/user/MockAccountManagerImpl.java     |   4 +-
 ui/public/locales/en.json                          |   3 +
 ui/src/components/view/InfoCard.vue                |  18 +-
 ui/src/components/view/SearchView.vue              |  13 +-
 ui/src/config/section/account.js                   |  21 ++-
 ui/src/config/section/user.js                      |   7 +
 ui/src/views/iam/EditAccount.vue                   | 190 +++++++++++++++++++++
 ui/src/views/iam/EditUser.vue                      |  18 +-
 47 files changed, 894 insertions(+), 62 deletions(-)

diff --git a/api/src/main/java/com/cloud/event/EventTypes.java 
b/api/src/main/java/com/cloud/event/EventTypes.java
index 5e5309965c1..81ed185dae5 100644
--- a/api/src/main/java/com/cloud/event/EventTypes.java
+++ b/api/src/main/java/com/cloud/event/EventTypes.java
@@ -292,6 +292,7 @@ public class EventTypes {
 
     //register for user API and secret keys
     public static final String EVENT_REGISTER_FOR_SECRET_API_KEY = 
"REGISTER.USER.KEY";
+    public static final String API_KEY_ACCESS_UPDATE = "API.KEY.ACCESS.UPDATE";
 
     // Template Events
     public static final String EVENT_TEMPLATE_CREATE = "TEMPLATE.CREATE";
diff --git a/api/src/main/java/com/cloud/user/Account.java 
b/api/src/main/java/com/cloud/user/Account.java
index bb9838f137a..6be4d0a48f6 100644
--- a/api/src/main/java/com/cloud/user/Account.java
+++ b/api/src/main/java/com/cloud/user/Account.java
@@ -93,4 +93,8 @@ public interface Account extends ControlledEntity, 
InternalIdentity, Identity {
 
     boolean isDefault();
 
+    public void setApiKeyAccess(Boolean apiKeyAccess);
+
+    public Boolean getApiKeyAccess();
+
 }
diff --git a/api/src/main/java/com/cloud/user/AccountService.java 
b/api/src/main/java/com/cloud/user/AccountService.java
index 60db7abb734..e2c3bed0c29 100644
--- a/api/src/main/java/com/cloud/user/AccountService.java
+++ b/api/src/main/java/com/cloud/user/AccountService.java
@@ -19,6 +19,7 @@ package com.cloud.user;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.utils.Pair;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@@ -127,9 +128,9 @@ public interface AccountService {
      */
     UserAccount getUserAccountById(Long userId);
 
-    public Map<String, String> getKeys(GetUserKeysCmd cmd);
+    public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd);
 
-    public Map<String, String> getKeys(Long userId);
+    public Pair<Boolean, Map<String, String>> getKeys(Long userId);
 
     /**
      * Lists user two-factor authentication provider plugins
diff --git a/api/src/main/java/com/cloud/user/User.java 
b/api/src/main/java/com/cloud/user/User.java
index 422e264f10b..041b39ad272 100644
--- a/api/src/main/java/com/cloud/user/User.java
+++ b/api/src/main/java/com/cloud/user/User.java
@@ -94,4 +94,9 @@ public interface User extends OwnedBy, InternalIdentity {
     public boolean isUser2faEnabled();
 
     public String getKeyFor2fa();
+
+    public void setApiKeyAccess(Boolean apiKeyAccess);
+
+    public Boolean getApiKeyAccess();
+
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java 
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index a6c6991be24..8f78fe5c4b4 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -35,6 +35,7 @@ public class ApiConstants {
     public static final String ALLOW_USER_FORCE_STOP_VM = 
"allowuserforcestopvm";
     public static final String ANNOTATION = "annotation";
     public static final String API_KEY = "apikey";
+    public static final String API_KEY_ACCESS = "apikeyaccess";
     public static final String ARCHIVED = "archived";
     public static final String ARCH = "arch";
     public static final String AS_NUMBER = "asnumber";
@@ -1247,4 +1248,30 @@ public class ApiConstants {
     public enum DomainDetails {
         all, resource, min;
     }
+
+    public enum ApiKeyAccess {
+        DISABLED(false),
+        ENABLED(true),
+        INHERIT(null);
+
+        Boolean apiKeyAccess;
+
+        ApiKeyAccess(Boolean keyAccess) {
+            apiKeyAccess = keyAccess;
+        }
+
+        public Boolean toBoolean() {
+            return apiKeyAccess;
+        }
+
+        public static ApiKeyAccess fromBoolean(Boolean value) {
+            if (value == null) {
+                return INHERIT;
+            } else if (value) {
+                return ENABLED;
+            } else {
+                return DISABLED;
+            }
+        }
+    }
 }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java
index 91cbb90e4da..3347a0d09f3 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java
@@ -21,7 +21,9 @@ import java.util.Map;
 
 import javax.inject.Inject;
 
+import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.command.user.UserCmd;
 import org.apache.cloudstack.api.response.RoleResponse;
 
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@@ -40,8 +42,8 @@ import org.apache.cloudstack.region.RegionService;
 import com.cloud.user.Account;
 
 @APICommand(name = "updateAccount", description = "Updates account information 
for the authenticated user", responseObject = AccountResponse.class, entityType 
= {Account.class},
-        requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
-public class UpdateAccountCmd extends BaseCmd {
+        responseView = ResponseView.Restricted, requestHasSensitiveInfo = 
false, responseHasSensitiveInfo = true)
+public class UpdateAccountCmd extends BaseCmd implements UserCmd {
 
     /////////////////////////////////////////////////////
     //////////////// API parameters /////////////////////
@@ -70,6 +72,9 @@ public class UpdateAccountCmd extends BaseCmd {
     @Parameter(name = ApiConstants.ACCOUNT_DETAILS, type = CommandType.MAP, 
description = "Details for the account used to store specific parameters")
     private Map details;
 
+    @Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, 
description = "Determines if Api key access for this user is enabled, disabled 
or inherits the value from its parent, the domain level setting 
api.key.access", since = "4.20.1.0", authorized = {RoleType.Admin})
+    private String apiKeyAccess;
+
     @Inject
     RegionService _regionService;
 
@@ -109,6 +114,10 @@ public class UpdateAccountCmd extends BaseCmd {
         return params;
     }
 
+    public String getApiKeyAccess() {
+        return apiKeyAccess;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -131,7 +140,7 @@ public class UpdateAccountCmd extends BaseCmd {
     public void execute() {
         Account result = _regionService.updateAccount(this);
         if (result != null){
-            AccountResponse response = 
_responseGenerator.createAccountResponse(ResponseView.Full, result);
+            AccountResponse response = 
_responseGenerator.createAccountResponse(getResponseView(), result);
             response.setResponseName(getCommandName());
             setResponseObject(response);
         } else {
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserKeysCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserKeysCmd.java
index 3a3414d95d8..cdd239f72b5 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserKeysCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserKeysCmd.java
@@ -20,6 +20,7 @@ package org.apache.cloudstack.api.command.admin.user;
 
 import com.cloud.user.Account;
 import com.cloud.user.User;
+import com.cloud.utils.Pair;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
@@ -54,11 +55,13 @@ public class GetUserKeysCmd extends BaseCmd{
         else return Account.ACCOUNT_ID_SYSTEM;
     }
     public void execute(){
-        Map<String, String> keys = _accountService.getKeys(this);
+        Pair<Boolean, Map<String, String>> keys = 
_accountService.getKeys(this);
+
         RegisterResponse response = new RegisterResponse();
         if(keys != null){
-            response.setApiKey(keys.get("apikey"));
-            response.setSecretKey(keys.get("secretkey"));
+            response.setApiKeyAccess(keys.first());
+            response.setApiKey(keys.second().get("apikey"));
+            response.setSecretKey(keys.second().get("secretkey"));
         }
 
         response.setObjectName("userkeys");
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java
index ef9e3fa2240..27a78c738c9 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java
@@ -19,20 +19,23 @@ package org.apache.cloudstack.api.command.admin.user;
 import com.cloud.server.ResourceIcon;
 import com.cloud.server.ResourceTag;
 import com.cloud.user.Account;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.command.user.UserCmd;
 import org.apache.cloudstack.api.response.ResourceIconResponse;
 
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseListAccountResourcesCmd;
 import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.UserResponse;
 
 import java.util.List;
 
 @APICommand(name = "listUsers", description = "Lists user accounts", 
responseObject = UserResponse.class,
-        requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
-public class ListUsersCmd extends BaseListAccountResourcesCmd {
+        responseView = ResponseView.Restricted, requestHasSensitiveInfo = 
false, responseHasSensitiveInfo = true)
+public class ListUsersCmd extends BaseListAccountResourcesCmd implements 
UserCmd {
 
 
     /////////////////////////////////////////////////////
@@ -53,6 +56,9 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd 
{
     @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, 
description = "List user by the username")
     private String username;
 
+    @Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, 
description = "List users by the Api key access value", since = "4.20.1.0", 
authorized = {RoleType.Admin})
+    private String apiKeyAccess;
+
     @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = 
CommandType.BOOLEAN,
             description = "flag to display the resource icon for users")
     private Boolean showIcon;
@@ -77,6 +83,10 @@ public class ListUsersCmd extends 
BaseListAccountResourcesCmd {
         return username;
     }
 
+    public String getApiKeyAccess() {
+        return apiKeyAccess;
+    }
+
     public Boolean getShowIcon() {
         return showIcon != null ? showIcon : false;
     }
@@ -87,7 +97,7 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd 
{
 
     @Override
     public void execute() {
-        ListResponse<UserResponse> response = 
_queryService.searchForUsers(this);
+        ListResponse<UserResponse> response = 
_queryService.searchForUsers(getResponseView(), this);
         response.setResponseName(getCommandName());
         this.setResponseObject(response);
         if (response != null && response.getCount() > 0 && getShowIcon()) {
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java
index c9e1e934152..3d7f51ae220 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java
@@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.admin.user;
 
 import javax.inject.Inject;
 
+import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
@@ -69,6 +70,9 @@ public class UpdateUserCmd extends BaseCmd {
     @Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, 
description = "The secret key for the user. Must be specified with userApiKey")
     private String secretKey;
 
+    @Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, 
description = "Determines if Api key access for this user is enabled, disabled 
or inherits the value from its parent, the owning account", since = "4.20.1.0", 
authorized = {RoleType.Admin})
+    private String apiKeyAccess;
+
     @Parameter(name = ApiConstants.TIMEZONE,
             type = CommandType.STRING,
             description = "Specifies a timezone for this command. For more 
information on the timezone parameter, see Time Zone Format.")
@@ -120,6 +124,10 @@ public class UpdateUserCmd extends BaseCmd {
         return secretKey;
     }
 
+    public String getApiKeyAccess() {
+        return apiKeyAccess;
+    }
+
     public String getTimezone() {
         return timezone;
     }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java
index 0a962b19e4f..9157188fdee 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
+import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
@@ -70,6 +71,9 @@ public class ListAccountsCmd extends 
BaseListDomainResourcesCmd implements UserC
                description = "comma separated list of account details 
requested, value can be a list of [ all, resource, min]")
     private List<String> viewDetails;
 
+    @Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, 
description = "List accounts by the Api key access value", since = "4.20.1.0", 
authorized = {RoleType.Admin})
+    private String apiKeyAccess;
+
     @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = 
CommandType.BOOLEAN,
             description = "flag to display the resource icon for accounts")
     private Boolean showIcon;
@@ -120,6 +124,10 @@ public class ListAccountsCmd extends 
BaseListDomainResourcesCmd implements UserC
         return dv;
     }
 
+    public String getApiKeyAccess() {
+        return apiKeyAccess;
+    }
+
     public boolean getShowIcon() {
         return showIcon != null ? showIcon : false;
     }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java
index 7a84e85a4a6..6fc098295f6 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java
@@ -271,6 +271,10 @@ public class AccountResponse extends BaseResponse 
implements ResourceLimitAndCou
     @Param(description = "The tagged resource limit and count for the 
account", since = "4.20.0")
     List<TaggedResourceLimitAndCountResponse> taggedResources;
 
+    @SerializedName(ApiConstants.API_KEY_ACCESS)
+    @Param(description = "whether api key access is Enabled, Disabled or set 
to Inherit (it inherits the value from the parent)", since = "4.20.1.0")
+    ApiConstants.ApiKeyAccess apiKeyAccess;
+
     @Override
     public String getObjectId() {
         return id;
@@ -554,4 +558,8 @@ public class AccountResponse extends BaseResponse 
implements ResourceLimitAndCou
     public void 
setTaggedResourceLimitsAndCounts(List<TaggedResourceLimitAndCountResponse> 
taggedResourceLimitsAndCounts) {
         this.taggedResources = taggedResourceLimitsAndCounts;
     }
+
+    public void setApiKeyAccess(Boolean apiKeyAccess) {
+        this.apiKeyAccess = 
ApiConstants.ApiKeyAccess.fromBoolean(apiKeyAccess);
+    }
 }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java
index 5faedabfc16..dd17cc5cc8a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java
@@ -18,19 +18,24 @@ package org.apache.cloudstack.api.response;
 
 import com.google.gson.annotations.SerializedName;
 
+import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseResponse;
 
 import com.cloud.serializer.Param;
 
 public class RegisterResponse extends BaseResponse {
-    @SerializedName("apikey")
+    @SerializedName(ApiConstants.API_KEY)
     @Param(description = "the api key of the registered user", isSensitive = 
true)
     private String apiKey;
 
-    @SerializedName("secretkey")
+    @SerializedName(ApiConstants.SECRET_KEY)
     @Param(description = "the secret key of the registered user", isSensitive 
= true)
     private String secretKey;
 
+    @SerializedName(ApiConstants.API_KEY_ACCESS)
+    @Param(description = "whether api key access is allowed or not", 
isSensitive = true)
+    private Boolean apiKeyAccess;
+
     public String getApiKey() {
         return apiKey;
     }
@@ -46,4 +51,8 @@ public class RegisterResponse extends BaseResponse {
     public void setSecretKey(String secretKey) {
         this.secretKey = secretKey;
     }
+
+    public void setApiKeyAccess(Boolean apiKeyAccess) {
+        this.apiKeyAccess = apiKeyAccess;
+    }
 }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
index 1a17f3b8698..df97a915700 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
@@ -128,6 +128,10 @@ public class UserResponse extends BaseResponse implements 
SetResourceIconRespons
     @Param(description = "true if user has two factor authentication is 
mandated", since = "4.18.0.0")
     private Boolean is2FAmandated;
 
+    @SerializedName(ApiConstants.API_KEY_ACCESS)
+    @Param(description = "whether api key access is Enabled, Disabled or set 
to Inherit (it inherits the value from the parent)", since = "4.20.1.0")
+    ApiConstants.ApiKeyAccess apiKeyAccess;
+
     @Override
     public String getObjectId() {
         return this.getId();
@@ -309,4 +313,8 @@ public class UserResponse extends BaseResponse implements 
SetResourceIconRespons
     public void set2FAmandated(Boolean is2FAmandated) {
         this.is2FAmandated = is2FAmandated;
     }
+
+    public void setApiKeyAccess(Boolean apiKeyAccess) {
+        this.apiKeyAccess = 
ApiConstants.ApiKeyAccess.fromBoolean(apiKeyAccess);
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java 
b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
index c93e43d9f37..88081494320 100644
--- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java
+++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
@@ -19,6 +19,7 @@ package org.apache.cloudstack.query;
 import java.util.List;
 
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
+import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
 import org.apache.cloudstack.api.command.admin.host.ListHostTagsCmd;
 import org.apache.cloudstack.api.command.admin.host.ListHostsCmd;
@@ -130,7 +131,7 @@ public interface QueryService {
     ConfigKey<Boolean> ReturnVmStatsOnVmList = new ConfigKey<>("Advanced", 
Boolean.class, "list.vm.default.details.stats", "true",
             "Determines whether VM stats should be returned when details are 
not explicitly specified in listVirtualMachines API request. When false, 
details default to [group, nics, secgrp, tmpl, servoff, diskoff, backoff, iso, 
volume, min, affgrp]. When true, all details are returned including 'stats'.", 
true, ConfigKey.Scope.Global);
 
-    ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws 
PermissionDeniedException;
+    ListResponse<UserResponse> searchForUsers(ResponseObject.ResponseView 
responseView, ListUsersCmd cmd) throws PermissionDeniedException;
 
     ListResponse<UserResponse> searchForUsers(Long domainId, boolean 
recursive) throws PermissionDeniedException;
 
diff --git 
a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java 
b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
index cb219007325..abf86043937 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
@@ -88,6 +88,7 @@ import com.cloud.upgrade.dao.Upgrade41800to41810;
 import com.cloud.upgrade.dao.Upgrade41810to41900;
 import com.cloud.upgrade.dao.Upgrade41900to41910;
 import com.cloud.upgrade.dao.Upgrade41910to42000;
+import com.cloud.upgrade.dao.Upgrade42000to42010;
 import com.cloud.upgrade.dao.Upgrade420to421;
 import com.cloud.upgrade.dao.Upgrade421to430;
 import com.cloud.upgrade.dao.Upgrade430to440;
@@ -230,6 +231,7 @@ public class DatabaseUpgradeChecker implements 
SystemIntegrityChecker {
                 .next("4.18.1.0", new Upgrade41810to41900())
                 .next("4.19.0.0", new Upgrade41900to41910())
                 .next("4.19.1.0", new Upgrade41910to42000())
+                .next("4.20.0.0", new Upgrade42000to42010())
                 .build();
     }
 
diff --git 
a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42000to42010.java 
b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42000to42010.java
new file mode 100644
index 00000000000..197ca1cb34c
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42000to42010.java
@@ -0,0 +1,83 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.upgrade.dao;
+
+import java.io.InputStream;
+import java.sql.Connection;
+
+import com.cloud.upgrade.SystemVmTemplateRegistration;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class Upgrade42000to42010 extends DbUpgradeAbstractImpl implements 
DbUpgrade, DbUpgradeSystemVmTemplate {
+    private SystemVmTemplateRegistration systemVmTemplateRegistration;
+
+    @Override
+    public String[] getUpgradableVersionRange() {
+        return new String[] {"4.20.0.0", "4.20.1.0"};
+    }
+
+    @Override
+    public String getUpgradedVersion() {
+        return "4.20.1.0";
+    }
+
+    @Override
+    public boolean supportsRollingUpgrade() {
+        return false;
+    }
+
+    @Override
+    public InputStream[] getPrepareScripts() {
+        final String scriptFile = "META-INF/db/schema-42000to42010.sql";
+        final InputStream script = 
Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile);
+        if (script == null) {
+            throw new CloudRuntimeException("Unable to find " + scriptFile);
+        }
+
+        return new InputStream[] {script};
+    }
+
+    @Override
+    public void performDataMigration(Connection conn) {
+    }
+
+    @Override
+    public InputStream[] getCleanupScripts() {
+        final String scriptFile = 
"META-INF/db/schema-42000to42010-cleanup.sql";
+        final InputStream script = 
Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile);
+        if (script == null) {
+            throw new CloudRuntimeException("Unable to find " + scriptFile);
+        }
+
+        return new InputStream[] {script};
+    }
+
+    private void initSystemVmTemplateRegistration() {
+        systemVmTemplateRegistration = new SystemVmTemplateRegistration("");
+    }
+
+    @Override
+    public void updateSystemVmTemplates(Connection conn) {
+        logger.debug("Updating System Vm template IDs");
+        initSystemVmTemplateRegistration();
+        try {
+            systemVmTemplateRegistration.updateSystemVmTemplates(conn);
+        } catch (Exception e) {
+            throw new CloudRuntimeException("Failed to find / register 
SystemVM template(s)");
+        }
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/user/AccountVO.java 
b/engine/schema/src/main/java/com/cloud/user/AccountVO.java
index f04b2bafbde..74a538565d7 100644
--- a/engine/schema/src/main/java/com/cloud/user/AccountVO.java
+++ b/engine/schema/src/main/java/com/cloud/user/AccountVO.java
@@ -77,6 +77,9 @@ public class AccountVO implements Account {
     @Column(name = "default")
     boolean isDefault;
 
+    @Column(name = "api_key_access")
+    private Boolean apiKeyAccess;
+
     public AccountVO() {
         uuid = UUID.randomUUID().toString();
     }
@@ -229,4 +232,14 @@ public class AccountVO implements Account {
     public String reflectionToString() {
         return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, 
"id", "uuid", "accountName", "domainId");
     }
+
+    @Override
+    public void setApiKeyAccess(Boolean apiKeyAccess) {
+        this.apiKeyAccess = apiKeyAccess;
+    }
+
+    @Override
+    public Boolean getApiKeyAccess() {
+        return apiKeyAccess;
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/user/UserVO.java 
b/engine/schema/src/main/java/com/cloud/user/UserVO.java
index 69970bf2d2c..7dac26429ac 100644
--- a/engine/schema/src/main/java/com/cloud/user/UserVO.java
+++ b/engine/schema/src/main/java/com/cloud/user/UserVO.java
@@ -115,6 +115,9 @@ public class UserVO implements User, Identity, 
InternalIdentity {
     @Column(name = "key_for_2fa")
     private String keyFor2fa;
 
+    @Column(name = "api_key_access")
+    private Boolean apiKeyAccess;
+
     public UserVO() {
         this.uuid = UUID.randomUUID().toString();
     }
@@ -350,4 +353,13 @@ public class UserVO implements User, Identity, 
InternalIdentity {
         this.user2faProvider = user2faProvider;
     }
 
+    @Override
+    public void setApiKeyAccess(Boolean apiKeyAccess) {
+        this.apiKeyAccess = apiKeyAccess;
+    }
+
+    @Override
+    public Boolean getApiKeyAccess() {
+        return apiKeyAccess;
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java 
b/engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java
index eed5572a0b2..f9ef5c40eba 100644
--- a/engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java
@@ -41,8 +41,8 @@ import java.util.List;
 
 @Component
 public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements 
AccountDao {
-    private static final String FIND_USER_ACCOUNT_BY_API_KEY = "SELECT u.id, 
u.username, u.account_id, u.secret_key, u.state, "
-        + "a.id, a.account_name, a.type, a.role_id, a.domain_id, a.state " + 
"FROM `cloud`.`user` u, `cloud`.`account` a "
+    private static final String FIND_USER_ACCOUNT_BY_API_KEY = "SELECT u.id, 
u.username, u.account_id, u.secret_key, u.state, u.api_key_access, "
+        + "a.id, a.account_name, a.type, a.role_id, a.domain_id, a.state, 
a.api_key_access " + "FROM `cloud`.`user` u, `cloud`.`account` a "
         + "WHERE u.account_id = a.id AND u.api_key = ? and u.removed IS NULL";
 
     protected final SearchBuilder<AccountVO> AllFieldsSearch;
@@ -148,13 +148,25 @@ public class AccountDaoImpl extends 
GenericDaoBase<AccountVO, Long> implements A
                 u.setAccountId(rs.getLong(3));
                 u.setSecretKey(DBEncryptionUtil.decrypt(rs.getString(4)));
                 u.setState(State.getValueOf(rs.getString(5)));
-
-                AccountVO a = new AccountVO(rs.getLong(6));
-                a.setAccountName(rs.getString(7));
-                a.setType(Account.Type.getFromValue(rs.getInt(8)));
-                a.setRoleId(rs.getLong(9));
-                a.setDomainId(rs.getLong(10));
-                a.setState(State.getValueOf(rs.getString(11)));
+                boolean apiKeyAccess = rs.getBoolean(6);
+                if (rs.wasNull()) {
+                    u.setApiKeyAccess(null);
+                } else {
+                    u.setApiKeyAccess(apiKeyAccess);
+                }
+
+                AccountVO a = new AccountVO(rs.getLong(7));
+                a.setAccountName(rs.getString(8));
+                a.setType(Account.Type.getFromValue(rs.getInt(9)));
+                a.setRoleId(rs.getLong(10));
+                a.setDomainId(rs.getLong(11));
+                a.setState(State.getValueOf(rs.getString(12)));
+                apiKeyAccess = rs.getBoolean(13);
+                if (rs.wasNull()) {
+                    a.setApiKeyAccess(null);
+                } else {
+                    a.setApiKeyAccess(apiKeyAccess);
+                }
 
                 userAcctPair = new Pair<User, Account>(u, a);
             }
diff --git 
a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010-cleanup.sql 
b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010-cleanup.sql
new file mode 100644
index 00000000000..d187b6fa043
--- /dev/null
+++ 
b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010-cleanup.sql
@@ -0,0 +1,20 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+--;
+-- Schema upgrade cleanup from 4.20.0.0 to 4.20.1.0
+--;
diff --git 
a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql 
b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
new file mode 100644
index 00000000000..31c4928d81b
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
@@ -0,0 +1,24 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+--;
+-- Schema upgrade from 4.20.0.0 to 4.20.1.0
+--;
+
+-- Add column api_key_access to user and account tables
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user', 'api_key_access', 'boolean 
DEFAULT NULL COMMENT "is api key access allowed for the user" AFTER 
`secret_key`');
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.account', 'api_key_access', 
'boolean DEFAULT NULL COMMENT "is api key access allowed for the account" ');
diff --git 
a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql 
b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql
index 87546a9d118..dc64380fb57 100644
--- a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql
@@ -31,6 +31,7 @@ select
     `account`.`cleanup_needed` AS `cleanup_needed`,
     `account`.`network_domain` AS `network_domain` ,
     `account`.`default` AS `default`,
+    `account`.`api_key_access` AS `api_key_access`,
     `domain`.`id` AS `domain_id`,
     `domain`.`uuid` AS `domain_uuid`,
     `domain`.`name` AS `domain_name`,
diff --git 
a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql 
b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql
index 7eedc03712b..340cfa9055f 100644
--- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql
@@ -39,6 +39,7 @@ select
     user.incorrect_login_attempts,
     user.source,
     user.default,
+    user.api_key_access,
     account.id account_id,
     account.uuid account_uuid,
     account.account_name account_name,
diff --git 
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
 
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
index 36a8050754c..00cf56345c8 100644
--- 
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
+++ 
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
@@ -34,6 +34,7 @@ public class ConfigKey<T> {
     public static final String CATEGORY_ADVANCED = "Advanced";
     public static final String CATEGORY_ALERT = "Alert";
     public static final String CATEGORY_NETWORK = "Network";
+    public static final String CATEGORY_SYSTEM = "System";
 
     public enum Scope {
         Global, Zone, Cluster, StoragePool, Account, ManagementServer, 
ImageStore, Domain
diff --git 
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
index 3a5541654bb..7d27e6b77ce 100644
--- 
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
@@ -486,12 +486,12 @@ public class MockAccountManager extends ManagerBase 
implements AccountManager {
     }
 
     @Override
-    public Map<String, String> getKeys(GetUserKeysCmd cmd){
+    public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd){
         return null;
     }
 
     @Override
-    public Map<String, String> getKeys(Long userId) {
+    public Pair<Boolean, Map<String, String>> getKeys(Long userId) {
         return null;
     }
 
diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java 
b/server/src/main/java/com/cloud/api/ApiDBUtils.java
index a169ebc0f19..944f60d292c 100644
--- a/server/src/main/java/com/cloud/api/ApiDBUtils.java
+++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java
@@ -1945,11 +1945,11 @@ public class ApiDBUtils {
     }
 
     public static UserResponse newUserResponse(UserAccountJoinVO usr) {
-        return newUserResponse(usr, null);
+        return newUserResponse(ResponseView.Restricted, null, usr);
     }
 
-    public static UserResponse newUserResponse(UserAccountJoinVO usr, Long 
domainId) {
-        UserResponse response = s_userAccountJoinDao.newUserResponse(usr);
+    public static UserResponse newUserResponse(ResponseView view, Long 
domainId, UserAccountJoinVO usr) {
+        UserResponse response = s_userAccountJoinDao.newUserResponse(view, 
usr);
         if(!AccountManager.UseSecretKeyInResponse.value()){
             response.setSecretKey(null);
         }
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java 
b/server/src/main/java/com/cloud/api/ApiServer.java
index 72e97c3a6ee..98f87dfc3f0 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -188,6 +188,7 @@ import com.cloud.utils.exception.ExceptionProxyObject;
 import com.cloud.utils.net.NetUtils;
 import com.google.gson.reflect.TypeToken;
 
+import static com.cloud.user.AccountManagerImpl.apiKeyAccess;
 import static 
org.apache.cloudstack.user.UserPasswordResetManager.UserPasswordResetEnabled;
 
 @Component
@@ -896,6 +897,34 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
         }
     }
 
+    protected boolean verifyApiKeyAccessAllowed(User user, Account account) {
+        Boolean apiKeyAccessEnabled = user.getApiKeyAccess();
+        if (apiKeyAccessEnabled != null) {
+            if (Boolean.TRUE.equals(apiKeyAccessEnabled)) {
+                return true;
+            } else {
+                logger.info("Api-Key access is disabled for the User " + 
user.toString());
+                return false;
+            }
+        }
+        apiKeyAccessEnabled = account.getApiKeyAccess();
+        if (apiKeyAccessEnabled != null) {
+            if (Boolean.TRUE.equals(apiKeyAccessEnabled)) {
+                return true;
+            } else {
+                logger.info("Api-Key access is disabled for the Account " + 
account.toString());
+                return false;
+            }
+        }
+        apiKeyAccessEnabled = apiKeyAccess.valueIn(account.getDomainId());
+        if (Boolean.TRUE.equals(apiKeyAccessEnabled)) {
+                return true;
+        } else {
+            logger.info("Api-Key access is disabled by the Domain level 
setting api.key.access");
+        }
+        return false;
+    }
+
     @Override
     public boolean verifyRequest(final Map<String, Object[]> 
requestParameters, final Long userId, InetAddress remoteAddress) throws 
ServerApiException {
         try {
@@ -1012,6 +1041,10 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
                 return false;
             }
 
+            if (!verifyApiKeyAccessAllowed(user, account)) {
+                return false;
+            }
+
             if (!commandAvailable(remoteAddress, commandName, user)) {
                 return false;
             }
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java 
b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index 25018bc2c36..976d3817a0a 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -661,10 +661,13 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
      * .api.command.admin.user.ListUsersCmd)
      */
     @Override
-    public ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws 
PermissionDeniedException {
+    public ListResponse<UserResponse> searchForUsers(ResponseView 
responseView, ListUsersCmd cmd) throws PermissionDeniedException {
         Pair<List<UserAccountJoinVO>, Integer> result = 
searchForUsersInternal(cmd);
         ListResponse<UserResponse> response = new ListResponse<UserResponse>();
-        List<UserResponse> userResponses = 
ViewResponseHelper.createUserResponse(CallContext.current().getCallingAccount().getDomainId(),
+        if (CallContext.current().getCallingAccount().getType() == 
Account.Type.ADMIN) {
+            responseView = ResponseView.Full;
+        }
+        List<UserResponse> userResponses = 
ViewResponseHelper.createUserResponse(responseView, 
CallContext.current().getCallingAccount().getDomainId(),
                 result.first().toArray(new 
UserAccountJoinVO[result.first().size()]));
         response.setResponses(userResponses, result.second());
         return response;
@@ -691,10 +694,10 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         Object state = null;
         String keyword = null;
 
-        Pair<List<UserAccountJoinVO>, Integer> result =  
getUserListInternal(caller, permittedAccounts, listAll, id, username, type, 
accountName, state, keyword, domainId, recursive,
-                null);
+        Pair<List<UserAccountJoinVO>, Integer> result =  
getUserListInternal(caller, permittedAccounts, listAll, id,
+                username, type, accountName, state, keyword, null, domainId, 
recursive, null);
         ListResponse<UserResponse> response = new ListResponse<UserResponse>();
-        List<UserResponse> userResponses = 
ViewResponseHelper.createUserResponse(CallContext.current().getCallingAccount().getDomainId(),
+        List<UserResponse> userResponses = 
ViewResponseHelper.createUserResponse(ResponseView.Restricted, 
CallContext.current().getCallingAccount().getDomainId(),
                 result.first().toArray(new 
UserAccountJoinVO[result.first().size()]));
         response.setResponses(userResponses, result.second());
         return response;
@@ -719,6 +722,7 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         String accountName = cmd.getAccountName();
         Object state = cmd.getState();
         String keyword = cmd.getKeyword();
+        String apiKeyAccess = cmd.getApiKeyAccess();
 
         Long domainId = cmd.getDomainId();
         boolean recursive = cmd.isRecursive();
@@ -727,11 +731,11 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
 
         Filter searchFilter = new Filter(UserAccountJoinVO.class, "id", true, 
startIndex, pageSizeVal);
 
-        return getUserListInternal(caller, permittedAccounts, listAll, id, 
username, type, accountName, state, keyword, domainId, recursive, searchFilter);
+        return getUserListInternal(caller, permittedAccounts, listAll, id, 
username, type, accountName, state, keyword, apiKeyAccess, domainId, recursive, 
searchFilter);
     }
 
     private Pair<List<UserAccountJoinVO>, Integer> getUserListInternal(Account 
caller, List<Long> permittedAccounts, boolean listAll, Long id, Object 
username, Object type,
-            String accountName, Object state, String keyword, Long domainId, 
boolean recursive, Filter searchFilter) {
+            String accountName, Object state, String keyword, String 
apiKeyAccess, Long domainId, boolean recursive, Filter searchFilter) {
         Ternary<Long, Boolean, ListProjectResourcesCriteria> 
domainIdRecursiveListProject = new Ternary<Long, Boolean, 
ListProjectResourcesCriteria>(domainId, recursive, null);
         accountMgr.buildACLSearchParameters(caller, id, accountName, null, 
permittedAccounts, domainIdRecursiveListProject, listAll, false);
         domainId = domainIdRecursiveListProject.first();
@@ -757,6 +761,9 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         sb.and("domainId", sb.entity().getDomainId(), Op.EQ);
         sb.and("accountName", sb.entity().getAccountName(), Op.EQ);
         sb.and("state", sb.entity().getState(), Op.EQ);
+        if (apiKeyAccess != null) {
+            sb.and("apiKeyAccess", sb.entity().getApiKeyAccess(), Op.EQ);
+        }
 
         if ((accountName == null) && (domainId != null)) {
             sb.and("domainPath", sb.entity().getDomainPath(), Op.LIKE);
@@ -811,6 +818,15 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
             sc.setParameters("state", state);
         }
 
+        if (apiKeyAccess != null) {
+            try {
+                ApiConstants.ApiKeyAccess access = 
ApiConstants.ApiKeyAccess.valueOf(apiKeyAccess.toUpperCase());
+                sc.setParameters("apiKeyAccess", access.toBoolean());
+            } catch (IllegalArgumentException ex) {
+                throw new InvalidParameterValueException("ApiKeyAccess value 
can only be Enabled/Disabled/Inherit");
+            }
+        }
+
         return _userAccountJoinDao.searchAndCount(sc, searchFilter);
     }
 
@@ -2897,6 +2913,7 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         Object state = cmd.getState();
         Object isCleanupRequired = cmd.isCleanupRequired();
         Object keyword = cmd.getKeyword();
+        String apiKeyAccess = cmd.getApiKeyAccess();
 
         SearchBuilder<AccountVO> accountSearchBuilder = 
_accountDao.createSearchBuilder();
         accountSearchBuilder.select(null, Func.DISTINCT, 
accountSearchBuilder.entity().getId()); // select distinct
@@ -2909,6 +2926,9 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         accountSearchBuilder.and("typeNEQ", 
accountSearchBuilder.entity().getType(), SearchCriteria.Op.NEQ);
         accountSearchBuilder.and("idNEQ", 
accountSearchBuilder.entity().getId(), SearchCriteria.Op.NEQ);
         accountSearchBuilder.and("type2NEQ", 
accountSearchBuilder.entity().getType(), SearchCriteria.Op.NEQ);
+        if (apiKeyAccess != null) {
+            accountSearchBuilder.and("apiKeyAccess", 
accountSearchBuilder.entity().getApiKeyAccess(), Op.EQ);
+        }
 
         if (domainId != null && isRecursive) {
             SearchBuilder<DomainVO> domainSearch = 
_domainDao.createSearchBuilder();
@@ -2972,6 +2992,15 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
             }
         }
 
+        if (apiKeyAccess != null) {
+            try {
+                ApiConstants.ApiKeyAccess access = 
ApiConstants.ApiKeyAccess.valueOf(apiKeyAccess.toUpperCase());
+                sc.setParameters("apiKeyAccess", access.toBoolean());
+            } catch (IllegalArgumentException ex) {
+                throw new InvalidParameterValueException("ApiKeyAccess value 
can only be Enabled/Disabled/Inherit");
+            }
+        }
+
         Pair<List<AccountVO>, Integer> uniqueAccountPair = 
_accountDao.searchAndCount(sc, searchFilter);
         Integer count = uniqueAccountPair.second();
         List<Long> accountIds = 
uniqueAccountPair.first().stream().map(AccountVO::getId).collect(Collectors.toList());
diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java 
b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
index db650bf7c3e..7d5658f6782 100644
--- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
@@ -105,13 +105,13 @@ public class ViewResponseHelper {
     protected Logger logger = LogManager.getLogger(getClass());
 
     public static List<UserResponse> createUserResponse(UserAccountJoinVO... 
users) {
-        return createUserResponse(null, users);
+        return createUserResponse(ResponseView.Restricted, null, users);
     }
 
-    public static List<UserResponse> createUserResponse(Long domainId, 
UserAccountJoinVO... users) {
+    public static List<UserResponse> createUserResponse(ResponseView 
responseView, Long domainId, UserAccountJoinVO... users) {
         List<UserResponse> respList = new ArrayList<UserResponse>();
         for (UserAccountJoinVO vt : users) {
-            respList.add(ApiDBUtils.newUserResponse(vt, domainId));
+            respList.add(ApiDBUtils.newUserResponse(responseView, domainId, 
vt));
         }
         return respList;
     }
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
index 7ffd3ef319f..07b5c27438b 100644
--- a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
@@ -82,6 +82,9 @@ public class AccountJoinDaoImpl extends 
GenericDaoBase<AccountJoinVO, Long> impl
         accountResponse.setNetworkDomain(account.getNetworkDomain());
         accountResponse.setDefaultZone(account.getDataCenterUuid());
         accountResponse.setIsDefault(account.isDefault());
+        if (view == ResponseView.Full) {
+            accountResponse.setApiKeyAccess(account.getApiKeyAccess());
+        }
 
         // get network stat
         accountResponse.setBytesReceived(account.getBytesReceived());
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDao.java 
b/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDao.java
index b48f19272bc..cff758d0c17 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDao.java
@@ -18,6 +18,7 @@ package com.cloud.api.query.dao;
 
 import java.util.List;
 
+import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.response.UserResponse;
 
 import com.cloud.api.query.vo.UserAccountJoinVO;
@@ -27,7 +28,7 @@ import com.cloud.utils.db.GenericDao;
 
 public interface UserAccountJoinDao extends GenericDao<UserAccountJoinVO, 
Long> {
 
-    UserResponse newUserResponse(UserAccountJoinVO usr);
+    UserResponse newUserResponse(ResponseObject.ResponseView responseView, 
UserAccountJoinVO usr);
 
     UserAccountJoinVO newUserView(User usr);
 
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java
index c5b21f50d2d..f2c234b4c7c 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java
@@ -20,6 +20,7 @@ import java.util.List;
 
 
 import com.cloud.user.AccountManagerImpl;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.springframework.stereotype.Component;
 
 import org.apache.cloudstack.api.response.UserResponse;
@@ -52,7 +53,7 @@ public class UserAccountJoinDaoImpl extends 
GenericDaoBase<UserAccountJoinVO, Lo
     }
 
     @Override
-    public UserResponse newUserResponse(UserAccountJoinVO usr) {
+    public UserResponse newUserResponse(ResponseView view, UserAccountJoinVO 
usr) {
         UserResponse userResponse = new UserResponse();
         userResponse.setAccountId(usr.getAccountUuid());
         userResponse.setAccountName(usr.getAccountName());
@@ -75,6 +76,9 @@ public class UserAccountJoinDaoImpl extends 
GenericDaoBase<UserAccountJoinVO, Lo
         long domainId = usr.getDomainId();
         boolean is2FAmandated = 
Boolean.TRUE.equals(AccountManagerImpl.enableUserTwoFactorAuthentication.valueIn(domainId))
 && 
Boolean.TRUE.equals(AccountManagerImpl.mandateUserTwoFactorAuthentication.valueIn(domainId));
         userResponse.set2FAmandated(is2FAmandated);
+        if (view == ResponseView.Full) {
+            userResponse.setApiKeyAccess(usr.getApiKeyAccess());
+        }
 
         // set async job
         if (usr.getJobId() != null) {
diff --git a/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java 
b/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java
index 19c49294c84..0bd28d2af32 100644
--- a/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java
@@ -189,6 +189,9 @@ public class AccountJoinVO extends BaseViewVO implements 
InternalIdentity, Ident
     @Column(name = "default")
     boolean isDefault;
 
+    @Column(name = "api_key_access")
+    Boolean apiKeyAccess;
+
     public AccountJoinVO() {
     }
 
@@ -393,4 +396,8 @@ public class AccountJoinVO extends BaseViewVO implements 
InternalIdentity, Ident
     public boolean isDefault() {
         return isDefault;
     }
+
+    public Boolean getApiKeyAccess() {
+        return apiKeyAccess;
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java 
b/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java
index 3a82980725b..ad005eebb76 100644
--- a/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java
@@ -133,6 +133,9 @@ public class UserAccountJoinVO extends BaseViewVO 
implements InternalIdentity, I
     @Column(name = "is_user_2fa_enabled")
     boolean user2faEnabled;
 
+    @Column(name = "api_key_access")
+    Boolean apiKeyAccess;
+
     public UserAccountJoinVO() {
     }
 
@@ -281,4 +284,8 @@ public class UserAccountJoinVO extends BaseViewVO 
implements InternalIdentity, I
     public boolean isUser2faEnabled() {
         return user2faEnabled;
     }
+
+    public Boolean getApiKeyAccess() {
+        return apiKeyAccess;
+    }
 }
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 31b9a7624ad..02abc507fdb 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -53,6 +53,7 @@ import org.apache.cloudstack.agent.lb.IndirectAgentLB;
 import org.apache.cloudstack.agent.lb.IndirectAgentLBServiceImpl;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
 import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
@@ -310,6 +311,7 @@ import com.googlecode.ipv6.IPv6Network;
 import static 
com.cloud.configuration.Config.SecStorageAllowedInternalDownloadSites;
 import static com.cloud.offering.NetworkOffering.RoutingMode.Dynamic;
 import static com.cloud.offering.NetworkOffering.RoutingMode.Static;
+import static org.apache.cloudstack.framework.config.ConfigKey.CATEGORY_SYSTEM;
 
 public class ConfigurationManagerImpl extends ManagerBase implements 
ConfigurationManager, ConfigurationService, Configurable {
     public static final String PERACCOUNT = "peraccount";
@@ -708,6 +710,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 value = DBEncryptionUtil.encrypt(value);
             }
 
+            ApiCommandResourceType resourceType;
             ConfigKey.Scope scopeVal = ConfigKey.Scope.valueOf(scope);
             switch (scopeVal) {
             case Zone:
@@ -715,6 +718,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 if (zone == null) {
                     throw new InvalidParameterValueException("unable to find 
zone by id " + resourceId);
                 }
+                resourceType = ApiCommandResourceType.Zone;
                 _dcDetailsDao.addDetail(resourceId, name, value, true);
                 break;
             case Cluster:
@@ -722,6 +726,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 if (cluster == null) {
                     throw new InvalidParameterValueException("unable to find 
cluster by id " + resourceId);
                 }
+                resourceType = ApiCommandResourceType.Cluster;
                 String newName = name;
                 if (name.equalsIgnoreCase("cpu.overprovisioning.factor")) {
                     newName = "cpuOvercommitRatio";
@@ -744,6 +749,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 if (pool == null) {
                     throw new InvalidParameterValueException("unable to find 
storage pool by id " + resourceId);
                 }
+                resourceType = ApiCommandResourceType.StoragePool;
                 
if(name.equals(CapacityManager.StorageOverprovisioningFactor.key())) {
                     if(!pool.getPoolType().supportsOverProvisioning() ) {
                         throw new InvalidParameterValueException("Unable to 
update storage pool with id " + resourceId + ". Overprovision not supported for 
" + pool.getPoolType());
@@ -765,6 +771,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 if (account == null) {
                     throw new InvalidParameterValueException("unable to find 
account by id " + resourceId);
                 }
+                resourceType = ApiCommandResourceType.Account;
                 AccountDetailVO accountDetailVO = 
_accountDetailsDao.findDetail(resourceId, name);
                 if (accountDetailVO == null) {
                     accountDetailVO = new AccountDetailVO(resourceId, name, 
value);
@@ -778,6 +785,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
             case ImageStore:
                 final ImageStoreVO imgStore = 
_imageStoreDao.findById(resourceId);
                 Preconditions.checkState(imgStore != null);
+                resourceType = ApiCommandResourceType.ImageStore;
                 _imageStoreDetailsDao.addDetail(resourceId, name, value, true);
                 break;
 
@@ -786,6 +794,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 if (domain == null) {
                     throw new InvalidParameterValueException("unable to find 
domain by id " + resourceId);
                 }
+                resourceType = ApiCommandResourceType.Domain;
                 DomainDetailVO domainDetailVO = 
_domainDetailsDao.findDetail(resourceId, name);
                 if (domainDetailVO == null) {
                     domainDetailVO = new DomainDetailVO(resourceId, name, 
value);
@@ -800,6 +809,10 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 throw new InvalidParameterValueException("Scope provided is 
invalid");
             }
 
+            CallContext.current().setEventResourceType(resourceType);
+            CallContext.current().setEventResourceId(resourceId);
+            CallContext.current().setEventDetails(String.format(" Name: %s, 
New Value: %s, Scope: %s", name, value, scope));
+
             _configDepot.invalidateConfigCache(name, scopeVal, resourceId);
             return valueEncrypted ? DBEncryptionUtil.decrypt(value) : value;
         }
@@ -957,6 +970,11 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
             category = config.getCategory();
         }
 
+        if (CATEGORY_SYSTEM.equals(category) && 
!_accountMgr.isRootAdmin(caller.getId())) {
+            logger.warn("Only Root Admin is allowed to edit the configuration 
" + name);
+            throw new CloudRuntimeException("Only Root Admin is allowed to 
edit this configuration.");
+        }
+
         if (value == null) {
             return _configDao.findByName(name);
         }
@@ -1008,7 +1026,6 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         if (value.isEmpty() || value.equals("null")) {
             value = (id == null) ? null : "";
         }
-
         final String updatedValue = updateConfiguration(userId, name, 
category, value, scope, id);
         if (value == null && updatedValue == null || 
updatedValue.equalsIgnoreCase(value)) {
             return _configDao.findByName(name);
diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java 
b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
index 39e8518f760..fa177428e51 100644
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@ -373,6 +373,13 @@ public class AccountManagerImpl extends ManagerBase 
implements AccountManager, M
             "totp",
             "The default user two factor authentication provider. Eg. totp, 
staticpin", true, ConfigKey.Scope.Domain);
 
+    public static final ConfigKey<Boolean> apiKeyAccess = new 
ConfigKey<>(ConfigKey.CATEGORY_SYSTEM, Boolean.class,
+            "api.key.access",
+            "true",
+            "Determines whether API (api-key/secret-key) access is allowed or 
not. Editable only by Root Admin.",
+            true,
+            ConfigKey.Scope.Domain);
+
     protected AccountManagerImpl() {
         super();
     }
@@ -1463,6 +1470,7 @@ public class AccountManagerImpl extends ManagerBase 
implements AccountManager, M
         logger.debug("Updating user with Id: " + user.getUuid());
 
         validateAndUpdateApiAndSecretKeyIfNeeded(updateUserCmd, user);
+        validateAndUpdateUserApiKeyAccess(updateUserCmd, user);
         Account account = retrieveAndValidateAccount(user);
 
         validateAndUpdateFirstNameIfNeeded(updateUserCmd, user);
@@ -1682,6 +1690,38 @@ public class AccountManagerImpl extends ManagerBase 
implements AccountManager, M
         user.setSecretKey(secretKey);
     }
 
+    protected void validateAndUpdateUserApiKeyAccess(UpdateUserCmd 
updateUserCmd, UserVO user) {
+        if (updateUserCmd.getApiKeyAccess() != null) {
+            try {
+                ApiConstants.ApiKeyAccess access = 
ApiConstants.ApiKeyAccess.valueOf(updateUserCmd.getApiKeyAccess().toUpperCase());
+                user.setApiKeyAccess(access.toBoolean());
+                Long callingUserId = CallContext.current().getCallingUserId();
+                Account callingAccount = 
CallContext.current().getCallingAccount();
+                ActionEventUtils.onActionEvent(callingUserId, 
callingAccount.getAccountId(), callingAccount.getDomainId(),
+                        EventTypes.API_KEY_ACCESS_UPDATE, "Api key access was 
changed for the User to " + access.toString(),
+                        user.getId(), ApiCommandResourceType.User.toString());
+            } catch (IllegalArgumentException ex) {
+                throw new InvalidParameterValueException("ApiKeyAccess value 
can only be Enabled/Disabled/Inherit");
+            }
+        }
+    }
+
+    protected void validateAndUpdateAccountApiKeyAccess(UpdateAccountCmd 
updateAccountCmd, AccountVO account) {
+        if (updateAccountCmd.getApiKeyAccess() != null) {
+            try {
+                ApiConstants.ApiKeyAccess access = 
ApiConstants.ApiKeyAccess.valueOf(updateAccountCmd.getApiKeyAccess().toUpperCase());
+                account.setApiKeyAccess(access.toBoolean());
+                Long callingUserId = CallContext.current().getCallingUserId();
+                Account callingAccount = 
CallContext.current().getCallingAccount();
+                ActionEventUtils.onActionEvent(callingUserId, 
callingAccount.getAccountId(), callingAccount.getDomainId(),
+                        EventTypes.API_KEY_ACCESS_UPDATE, "Api key access was 
changed for the Account to " + access.toString(),
+                        account.getId(), 
ApiCommandResourceType.Account.toString());
+            } catch (IllegalArgumentException ex) {
+                throw new InvalidParameterValueException("ApiKeyAccess value 
can only be Enabled/Disabled/Inherit");
+            }
+        }
+    }
+
     /**
      * Searches for a user with the given userId. If no user is found we throw 
an {@link InvalidParameterValueException}.
      */
@@ -2048,6 +2088,8 @@ public class AccountManagerImpl extends ManagerBase 
implements AccountManager, M
         Account caller = getCurrentCallingAccount();
         checkAccess(caller, _domainMgr.getDomain(account.getDomainId()));
 
+        validateAndUpdateAccountApiKeyAccess(cmd, acctForUpdate);
+
         if(newAccountName != null) {
 
             if (newAccountName.isEmpty()) {
@@ -2794,18 +2836,18 @@ public class AccountManagerImpl extends ManagerBase 
implements AccountManager, M
     }
 
     @Override
-    public Map<String, String> getKeys(GetUserKeysCmd cmd) {
+    public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd) {
         final long userId = cmd.getID();
         return getKeys(userId);
     }
 
     @Override
-    public Map<String, String> getKeys(Long userId) {
+    public Pair<Boolean, Map<String, String>> getKeys(Long userId) {
         User user = getActiveUser(userId);
         if (user == null) {
             throw new InvalidParameterValueException("Unable to find user by 
id");
         }
-        final ControlledEntity account = 
getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account 
from the userID of the requested user.
+        final Account account = 
getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account 
from the userID of the requested user.
         User caller = CallContext.current().getCallingUser();
         preventRootDomainAdminAccessToRootAdminKeys(caller, account);
         checkAccess(caller, account);
@@ -2814,7 +2856,15 @@ public class AccountManagerImpl extends ManagerBase 
implements AccountManager, M
         keys.put("apikey", user.getApiKey());
         keys.put("secretkey", user.getSecretKey());
 
-        return keys;
+        Boolean apiKeyAccess = user.getApiKeyAccess();
+        if (apiKeyAccess == null) {
+            apiKeyAccess = account.getApiKeyAccess();
+            if (apiKeyAccess == null) {
+                apiKeyAccess = 
AccountManagerImpl.apiKeyAccess.valueIn(account.getDomainId());
+            }
+        }
+
+        return new Pair<Boolean, Map<String, String>>(apiKeyAccess, keys);
     }
 
     protected void preventRootDomainAdminAccessToRootAdminKeys(User caller, 
ControlledEntity account) {
@@ -3320,7 +3370,7 @@ public class AccountManagerImpl extends ManagerBase 
implements AccountManager, M
     @Override
     public ConfigKey<?>[] getConfigKeys() {
         return new ConfigKey<?>[] {UseSecretKeyInResponse, 
enableUserTwoFactorAuthentication,
-                userTwoFactorAuthenticationDefaultProvider, 
mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer};
+                userTwoFactorAuthenticationDefaultProvider, 
mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer, 
apiKeyAccess};
     }
 
     public List<UserTwoFactorAuthenticator> 
getUserTwoFactorAuthenticationProviders() {
diff --git a/server/src/test/java/com/cloud/api/ApiServerTest.java 
b/server/src/test/java/com/cloud/api/ApiServerTest.java
index fed1d95a625..dedd6e02ec5 100644
--- a/server/src/test/java/com/cloud/api/ApiServerTest.java
+++ b/server/src/test/java/com/cloud/api/ApiServerTest.java
@@ -17,6 +17,8 @@
 package com.cloud.api;
 
 import com.cloud.domain.Domain;
+import com.cloud.user.Account;
+import com.cloud.user.User;
 import com.cloud.user.UserAccount;
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -147,4 +149,31 @@ public class ApiServerTest {
         Mockito.when(domain.getState()).thenReturn(Domain.State.Inactive);
         apiServer.forgotPassword(userAccount, domain);
     }
+
+    @Test
+    public void testVerifyApiKeyAccessAllowed() {
+        Long domainId = 1L;
+        User user = Mockito.mock(User.class);
+        Account account = Mockito.mock(Account.class);
+
+        Mockito.when(user.getApiKeyAccess()).thenReturn(true);
+        Assert.assertEquals(true, apiServer.verifyApiKeyAccessAllowed(user, 
account));
+        Mockito.verify(account, Mockito.never()).getApiKeyAccess();
+
+        Mockito.when(user.getApiKeyAccess()).thenReturn(false);
+        Assert.assertEquals(false, apiServer.verifyApiKeyAccessAllowed(user, 
account));
+        Mockito.verify(account, Mockito.never()).getApiKeyAccess();
+
+        Mockito.when(user.getApiKeyAccess()).thenReturn(null);
+        Mockito.when(account.getApiKeyAccess()).thenReturn(true);
+        Assert.assertEquals(true, apiServer.verifyApiKeyAccessAllowed(user, 
account));
+
+        Mockito.when(user.getApiKeyAccess()).thenReturn(null);
+        Mockito.when(account.getApiKeyAccess()).thenReturn(false);
+        Assert.assertEquals(false, apiServer.verifyApiKeyAccessAllowed(user, 
account));
+
+        Mockito.when(user.getApiKeyAccess()).thenReturn(null);
+        Mockito.when(account.getApiKeyAccess()).thenReturn(null);
+        Assert.assertEquals(true, apiServer.verifyApiKeyAccessAllowed(user, 
account));
+    }
 }
diff --git a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java 
b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
index f5de105e22c..42ea1ad4556 100644
--- a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
@@ -17,13 +17,18 @@
 
 package com.cloud.api.query;
 
+import com.cloud.api.ApiDBUtils;
 import com.cloud.api.query.dao.TemplateJoinDao;
+import com.cloud.api.query.dao.UserAccountJoinDao;
 import com.cloud.api.query.dao.UserVmJoinDao;
 import com.cloud.api.query.vo.EventJoinVO;
 import com.cloud.api.query.vo.TemplateJoinVO;
+import com.cloud.api.query.vo.UserAccountJoinVO;
 import com.cloud.api.query.vo.UserVmJoinVO;
 import com.cloud.dc.ClusterVO;
 import com.cloud.dc.dao.ClusterDao;
+import com.cloud.domain.DomainVO;
+import com.cloud.domain.dao.DomainDao;
 import com.cloud.event.EventVO;
 import com.cloud.event.dao.EventDao;
 import com.cloud.event.dao.EventJoinDao;
@@ -45,6 +50,7 @@ import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
+import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.Pair;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.db.Filter;
@@ -56,8 +62,11 @@ import com.cloud.vm.dao.VMInstanceDao;
 
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ResponseObject;
 import 
org.apache.cloudstack.api.command.admin.storage.ListObjectStoragePoolsCmd;
+import org.apache.cloudstack.api.command.admin.user.ListUsersCmd;
 import 
org.apache.cloudstack.api.command.admin.vm.ListAffectedVmsForStorageScopeChangeCmd;
+import org.apache.cloudstack.api.command.user.account.ListAccountsCmd;
 import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd;
 import org.apache.cloudstack.api.command.user.event.ListEventsCmd;
 import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd;
@@ -65,6 +74,7 @@ import 
org.apache.cloudstack.api.response.DetailOptionsResponse;
 import org.apache.cloudstack.api.response.EventResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.cloudstack.api.response.UserResponse;
 import org.apache.cloudstack.api.response.VirtualMachineResponse;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
@@ -150,6 +160,15 @@ public class QueryManagerImplTest {
     @Mock
     UserVmJoinDao userVmJoinDao;
 
+    @Mock
+    UserAccountJoinDao userAccountJoinDao;
+
+    @Mock
+    DomainDao domainDao;
+
+    @Mock
+    AccountDao accountDao;
+
     private AccountVO account;
     private UserVO user;
 
@@ -477,4 +496,79 @@ public class QueryManagerImplTest {
         Assert.assertEquals(response.getResponses().get(0).getId(), 
instanceUuid);
         Assert.assertEquals(response.getResponses().get(0).getName(), vmName);
     }
+
+    @Test
+    public void testSearchForUsers() {
+        ListUsersCmd cmd = Mockito.mock(ListUsersCmd.class);
+        String username = "Admin";
+        String accountName = "Admin";
+        Account.Type accountType = Account.Type.ADMIN;
+        Long domainId = 1L;
+        String apiKeyAccess = "Disabled";
+        Mockito.when(cmd.getUsername()).thenReturn(username);
+        Mockito.when(cmd.getAccountName()).thenReturn(accountName);
+        Mockito.when(cmd.getAccountType()).thenReturn(accountType);
+        Mockito.when(cmd.getDomainId()).thenReturn(domainId);
+        Mockito.when(cmd.getApiKeyAccess()).thenReturn(apiKeyAccess);
+
+        UserAccountJoinVO user = new UserAccountJoinVO();
+        DomainVO domain = Mockito.mock(DomainVO.class);
+        SearchBuilder<UserAccountJoinVO> sb = 
Mockito.mock(SearchBuilder.class);
+        SearchCriteria<UserAccountJoinVO> sc = 
Mockito.mock(SearchCriteria.class);
+        List<UserAccountJoinVO> users = new ArrayList<>();
+        Pair<List<UserAccountJoinVO>, Integer> result = new Pair<>(users, 0);
+        UserResponse response = Mockito.mock(UserResponse.class);
+
+        Mockito.when(userAccountJoinDao.createSearchBuilder()).thenReturn(sb);
+        Mockito.when(sb.entity()).thenReturn(user);
+        Mockito.when(sb.create()).thenReturn(sc);
+        
Mockito.when(userAccountJoinDao.searchAndCount(any(SearchCriteria.class), 
any(Filter.class))).thenReturn(result);
+
+        queryManager.searchForUsers(ResponseObject.ResponseView.Restricted, 
cmd);
+
+        Mockito.verify(sc).setParameters("username", username);
+        Mockito.verify(sc).setParameters("accountName", accountName);
+        Mockito.verify(sc).setParameters("type", accountType);
+        Mockito.verify(sc).setParameters("domainId", domainId);
+        Mockito.verify(sc).setParameters("apiKeyAccess", false);
+        Mockito.verify(userAccountJoinDao, Mockito.times(1)).searchAndCount(
+                any(SearchCriteria.class), any(Filter.class));
+    }
+
+    @Test
+    public void testSearchForAccounts() {
+        ListAccountsCmd cmd = Mockito.mock(ListAccountsCmd.class);
+        Long domainId = 1L;
+        String accountName = "Admin";
+        Account.Type accountType = Account.Type.ADMIN;
+        String apiKeyAccess = "Enabled";
+        Mockito.when(cmd.getId()).thenReturn(null);
+        Mockito.when(cmd.getDomainId()).thenReturn(domainId);
+        Mockito.when(cmd.getSearchName()).thenReturn(accountName);
+        Mockito.when(cmd.getAccountType()).thenReturn(accountType);
+        Mockito.when(cmd.getApiKeyAccess()).thenReturn(apiKeyAccess);
+
+        DomainVO domain = Mockito.mock(DomainVO.class);
+        SearchBuilder<AccountVO> sb = Mockito.mock(SearchBuilder.class);
+        SearchCriteria<AccountVO> sc = Mockito.mock(SearchCriteria.class);
+        Pair<List<AccountVO>, Integer> uniqueAccountPair = new Pair<>(new 
ArrayList<>(), 0);
+        Mockito.when(domainDao.findById(domainId)).thenReturn(domain);
+        Mockito.doNothing().when(accountManager).checkAccess(account, domain);
+
+        Mockito.when(accountDao.createSearchBuilder()).thenReturn(sb);
+        Mockito.when(sb.entity()).thenReturn(account);
+        Mockito.when(sb.create()).thenReturn(sc);
+        Mockito.when(accountDao.searchAndCount(any(SearchCriteria.class), 
any(Filter.class))).thenReturn(uniqueAccountPair);
+
+        try (MockedStatic<ApiDBUtils> apiDBUtilsMocked = 
Mockito.mockStatic(ApiDBUtils.class)) {
+            queryManager.searchForAccounts(cmd);
+        }
+
+        Mockito.verify(sc).setParameters("domainId", domainId);
+        Mockito.verify(sc).setParameters("accountName", accountName);
+        Mockito.verify(sc).setParameters("type", accountType);
+        Mockito.verify(sc).setParameters("apiKeyAccess", true);
+        Mockito.verify(accountDao, Mockito.times(1)).searchAndCount(
+                any(SearchCriteria.class), any(Filter.class));
+    }
 }
diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java 
b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
index 9daa19206fa..11fc69c538c 100644
--- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
+++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
@@ -26,7 +26,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.event.ActionEventUtils;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
 import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
 
 import org.apache.cloudstack.acl.ControlledEntity;
@@ -90,6 +92,9 @@ public class AccountManagerImplTest extends 
AccountManagetImplTestBase {
     @Mock
     private UpdateUserCmd UpdateUserCmdMock;
 
+    @Mock
+    private UpdateAccountCmd UpdateAccountCmdMock;
+
     private long userVoIdMock = 111l;
     @Mock
     private UserVO userVoMock;
@@ -507,6 +512,46 @@ public class AccountManagerImplTest extends 
AccountManagetImplTestBase {
         Mockito.verify(userVoMock).setSecretKey(secretKey);
     }
 
+    @Test
+    public void validateAndUpdatUserApiKeyAccess() {
+        Mockito.doReturn("Enabled").when(UpdateUserCmdMock).getApiKeyAccess();
+        try (MockedStatic<ActionEventUtils> eventUtils = 
Mockito.mockStatic(ActionEventUtils.class)) {
+            Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), 
Mockito.anyLong(),
+                    Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.anyString(),
+                    Mockito.anyLong(), Mockito.anyString())).thenReturn(1L);
+            
accountManagerImpl.validateAndUpdateUserApiKeyAccess(UpdateUserCmdMock, 
userVoMock);
+        }
+
+        Mockito.verify(userVoMock).setApiKeyAccess(true);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validateAndUpdatUserApiKeyAccessInvalidParameter() {
+        Mockito.doReturn("False").when(UpdateUserCmdMock).getApiKeyAccess();
+        
accountManagerImpl.validateAndUpdateUserApiKeyAccess(UpdateUserCmdMock, 
userVoMock);
+    }
+
+    @Test
+    public void validateAndUpdatAccountApiKeyAccess() {
+        
Mockito.doReturn("Inherit").when(UpdateAccountCmdMock).getApiKeyAccess();
+        try (MockedStatic<ActionEventUtils> eventUtils = 
Mockito.mockStatic(ActionEventUtils.class)) {
+            Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), 
Mockito.anyLong(),
+                    Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.anyString(),
+                    Mockito.anyLong(), Mockito.anyString())).thenReturn(1L);
+            
accountManagerImpl.validateAndUpdateAccountApiKeyAccess(UpdateAccountCmdMock, 
accountVoMock);
+        }
+
+        Mockito.verify(accountVoMock).setApiKeyAccess(null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validateAndUpdatAccountApiKeyAccessInvalidParameter() {
+        Mockito.doReturn("False").when(UpdateAccountCmdMock).getApiKeyAccess();
+        
accountManagerImpl.validateAndUpdateAccountApiKeyAccess(UpdateAccountCmdMock, 
accountVoMock);
+    }
+
     @Test(expected = CloudRuntimeException.class)
     public void retrieveAndValidateAccountTestAccountNotFound() {
         Mockito.doReturn(accountMockId).when(userVoMock).getAccountId();
diff --git a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java 
b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
index bd6632af1ca..30324b41986 100644
--- a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
+++ b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
@@ -450,12 +450,12 @@ public class MockAccountManagerImpl extends ManagerBase 
implements Manager, Acco
     }
 
     @Override
-    public Map<String, String> getKeys(GetUserKeysCmd cmd) {
+    public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd) {
         return null;
     }
 
     @Override
-    public Map<String, String> getKeys(Long userId) {
+    public Pair<Boolean, Map<String, String>> getKeys(Long userId) {
         return null;
     }
 
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index a4b5a860c08..e2f637bd410 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -32,6 +32,7 @@
 "label.accesskey": "Access key",
 "label.access.key": "Access key",
 "label.secret.key": "Secret key",
+"label.apikeyaccess": "Api Key Access",
 "label.account": "Account",
 "label.account.and.security.group": "Account - security group",
 "label.account.id": "Account ID",
@@ -882,6 +883,7 @@
 "label.edge": "Edge",
 "label.edge.zone": "Edge Zone",
 "label.edit": "Edit",
+"label.edit.account": "Edit Account",
 "label.edit.acl.list": "Edit ACL list",
 "label.edit.acl.rule": "Edit ACL rule",
 "label.edit.autoscale.vmprofile": "Edit AutoScale Instance Profile",
@@ -3549,6 +3551,7 @@
 "message.success.scale.kubernetes": "Successfully scaled Kubernetes cluster",
 "message.success.unmanage.instance": "Successfully unmanaged Instance",
 "message.success.unmanage.volume": "Successfully unmanaged Volume",
+"message.success.update.account": "Successfully updated Account",
 "message.success.update.bgp.peer": "Successfully updated BGP peer",
 "message.success.update.bucket": "Successfully updated bucket",
 "message.success.update.condition": "Successfully updated condition",
diff --git a/ui/src/components/view/InfoCard.vue 
b/ui/src/components/view/InfoCard.vue
index 06775d8efaf..974a278b456 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -733,8 +733,18 @@
         </div>
       </div>
 
-      <div class="account-center-tags" v-if="showKeys">
+      <div class="account-center-tags" v-if="showKeys || 
resource.apikeyaccess">
         <a-divider/>
+      </div>
+      <div class="account-center-tags" v-if="resource.apikeyaccess && 
resource.account">
+        <div class="resource-detail-item">
+          <div class="resource-detail-item__label">{{ $t('label.apikeyaccess') 
}}</div>
+          <div class="resource-detail-item__details">
+            <status class="status" :text="resource.apikeyaccess" displayText/>
+          </div>
+        </div>
+      </div>
+      <div class="account-center-tags" v-if="showKeys">
         <div class="user-keys">
           <key-outlined />
           <strong>
@@ -1083,6 +1093,9 @@ export default {
       api('getUserKeys', { id: this.resource.id }).then(json => {
         this.showKeys = true
         this.newResource.secretkey = 
json.getuserkeysresponse.userkeys.secretkey
+        if (!this.isAdmin()) {
+          this.newResource.apikeyaccess = 
json.getuserkeysresponse.userkeys.apikeyaccess ? 'Enabled' : 'Disabled'
+        }
         this.$emit('change-resource', this.newResource)
       })
     },
@@ -1113,6 +1126,9 @@ export default {
         (this.resource.domainid === this.$store.getters.userInfo.domainid && 
this.resource.account === this.$store.getters.userInfo.account) ||
         (this.resource.project && this.resource.projectid === 
this.$store.getters.project.id)
     },
+    isAdmin () {
+      return ['Admin'].includes(this.$store.getters.userInfo.roletype)
+    },
     showInput () {
       this.inputVisible = true
       this.$nextTick(function () {
diff --git a/ui/src/components/view/SearchView.vue 
b/ui/src/components/view/SearchView.vue
index c82b1172f2d..786a9f15be6 100644
--- a/ui/src/components/view/SearchView.vue
+++ b/ui/src/components/view/SearchView.vue
@@ -318,7 +318,7 @@ export default {
           type = 'list'
         } else if (item === 'tags') {
           type = 'tag'
-        } else if (item === 'resourcetype') {
+        } else if (['resourcetype', 'apikeyaccess'].includes(item)) {
           type = 'autocomplete'
         } else if (item === 'isencrypted') {
           type = 'boolean'
@@ -431,6 +431,17 @@ export default {
         ]
         this.fields[resourceTypeIndex].loading = false
       }
+
+      if (arrayField.includes('apikeyaccess')) {
+        const apiKeyAccessIndex = this.fields.findIndex(item => item.name === 
'apikeyaccess')
+        this.fields[apiKeyAccessIndex].loading = true
+        this.fields[apiKeyAccessIndex].opts = [
+          { value: 'Disabled' },
+          { value: 'Enabled' },
+          { value: 'Inherit' }
+        ]
+        this.fields[apiKeyAccessIndex].loading = false
+      }
     },
     async fetchDynamicFieldData (arrayField, searchKeyword) {
       const promises = []
diff --git a/ui/src/config/section/account.js b/ui/src/config/section/account.js
index 28c0e3f556d..21996167705 100644
--- a/ui/src/config/section/account.js
+++ b/ui/src/config/section/account.js
@@ -24,9 +24,15 @@ export default {
   icon: 'team-outlined',
   docHelp: 'adminguide/accounts.html',
   permission: ['listAccounts'],
-  searchFilters: ['name', 'accounttype', 'domainid'],
+  searchFilters: () => {
+    var filters = ['name', 'accounttype', 'domainid']
+    if (store.getters.userInfo.roletype === 'Admin') {
+      filters.push('apikeyaccess')
+    }
+    return filters
+  },
   columns: ['name', 'state', 'rolename', 'roletype', 'domainpath'],
-  details: ['name', 'id', 'rolename', 'roletype', 'domainpath', 
'networkdomain', 'iptotal', 'vmtotal', 'volumetotal', 'receivedbytes', 
'sentbytes', 'created'],
+  details: ['name', 'id', 'rolename', 'roletype', 'domainpath', 
'networkdomain', 'apikeyaccess', 'iptotal', 'vmtotal', 'volumetotal', 
'receivedbytes', 'sentbytes', 'created'],
   related: [{
     name: 'accountuser',
     title: 'label.users',
@@ -116,15 +122,8 @@ export default {
       icon: 'edit-outlined',
       label: 'label.action.edit.account',
       dataView: true,
-      args: ['newname', 'account', 'domainid', 'networkdomain', 'roleid'],
-      mapping: {
-        account: {
-          value: (record) => { return record.name }
-        },
-        domainid: {
-          value: (record) => { return record.domainid }
-        }
-      }
+      popup: true,
+      component: shallowRef(defineAsyncComponent(() => 
import('@/views/iam/EditAccount.vue')))
     },
     {
       api: 'updateResourceCount',
diff --git a/ui/src/config/section/user.js b/ui/src/config/section/user.js
index d8c4ac06d0c..60a55973f8c 100644
--- a/ui/src/config/section/user.js
+++ b/ui/src/config/section/user.js
@@ -25,6 +25,13 @@ export default {
   docHelp: 'adminguide/accounts.html#users',
   hidden: true,
   permission: ['listUsers'],
+  searchFilters: () => {
+    var filters = []
+    if (store.getters.userInfo.roletype === 'Admin') {
+      filters.push('apikeyaccess')
+    }
+    return filters
+  },
   columns: ['username', 'state', 'firstname', 'lastname', 'email', 'account', 
'domain'],
   details: ['username', 'id', 'firstname', 'lastname', 'email', 'usersource', 
'timezone', 'rolename', 'roletype', 'is2faenabled', 'account', 'domain', 
'created'],
   tabs: [
diff --git a/ui/src/views/iam/EditAccount.vue b/ui/src/views/iam/EditAccount.vue
new file mode 100644
index 00000000000..7775bb2b825
--- /dev/null
+++ b/ui/src/views/iam/EditAccount.vue
@@ -0,0 +1,190 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div class="form-layout" v-ctrl-enter="handleSubmit">
+    <a-spin :spinning="loading">
+      <a-form
+        :ref="formRef"
+        :model="form"
+        :loading="loading"
+        layout="vertical"
+        @finish="handleSubmit">
+        <a-form-item ref="newname" name="newname">
+          <template #label>
+            <tooltip-label :title="$t('label.newname')" 
:tooltip="apiParams.newname.description"/>
+          </template>
+          <a-input
+            v-model:value="form.newname"
+            :placeholder="apiParams.newname.description" />
+        </a-form-item>
+        <a-form-item ref="networkdomain" name="networkdomain">
+          <template #label>
+            <tooltip-label :title="$t('label.networkdomain')" 
:tooltip="apiParams.networkdomain.description"/>
+          </template>
+          <a-input
+            v-model:value="form.networkdomain"
+            :placeholder="apiParams.networkdomain.description" />
+        </a-form-item>
+        <a-form-item ref="roleid" name="roleid">
+          <template #label>
+            <tooltip-label :title="$t('label.role')" 
:tooltip="apiParams.roleid.description"/>
+          </template>
+          <a-select
+            v-model:value="form.roleid"
+            :loading="roleLoading"
+            :placeholder="apiParams.roleid.description"
+            v-focus="true"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) 
>= 0
+            }">
+            <a-select-option v-for="role in roles" :key="role.id" 
:value="role.id">{{ role.name }}</a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item v-if="isRootAdmin" ref="apikeyaccess" name="apikeyaccess">
+          <template #label>
+            <tooltip-label :title="$t('label.apikeyaccess')" 
:tooltip="apiParams.apikeyaccess.description"/>
+          </template>
+          <a-radio-group v-model:value="form.apikeyaccess" buttonStyle="solid">
+            <a-radio-button value="ENABLED">Enabled</a-radio-button>
+            <a-radio-button value="INHERIT">Inherit</a-radio-button>
+            <a-radio-button value="DISABLED">Disabled</a-radio-button>
+          </a-radio-group>
+        </a-form-item>
+        <div :span="24" class="action-button">
+          <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
+          <a-button :loading="loading" ref="submit" type="primary" 
@click="handleSubmit">{{ $t('label.ok') }}</a-button>
+        </div>
+      </a-form>
+    </a-spin>
+  </div>
+</template>
+<script>
+import { ref, reactive, toRaw } from 'vue'
+import { api } from '@/api'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+
+export default {
+  name: 'EditAccount',
+  components: {
+    TooltipLabel
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      loading: false,
+      roleLoading: false,
+      roles: []
+    }
+  },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('updateAccount')
+  },
+  created () {
+    this.initForm()
+    this.fetchData()
+  },
+  computed: {
+    isRootAdmin () {
+      return this.$store.getters.userInfo?.roletype === 'Admin'
+    }
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+    },
+    fetchData () {
+      this.account = this.resource.name
+      this.domainId = this.resource.domainid
+      this.form.apikeyaccess = this.resource.apikeyaccess
+      this.fetchRoles()
+    },
+    isValidValueForKey (obj, key) {
+      return key in obj && obj[key] != null
+    },
+    fetchRoles () {
+      this.roleLoading = true
+      const params = {}
+      params.state = 'enabled'
+      api('listRoles', params).then(response => {
+        this.roles = response.listrolesresponse.role || []
+        this.form.roleid = this.resource.roleid
+      }).finally(() => {
+        this.roleLoading = false
+      })
+    },
+    handleSubmit (e) {
+      e.preventDefault()
+      if (this.loading) return
+      this.formRef.value.validate().then(() => {
+        const values = toRaw(this.form)
+
+        this.loading = true
+        const params = {
+          newname: values.newname,
+          networkdomain: values.networkdomain,
+          roleid: values.roleid,
+          apikeyaccess: values.apikeyaccess,
+          account: this.account,
+          domainid: this.domainId
+        }
+        if (this.isValidValueForKey(values, 'networkdomain') && 
values.networkdomain.length > 0) {
+          params.networkdomain = values.networkdomain
+        }
+
+        api('updateAccount', params).then(response => {
+          this.$emit('refresh-data')
+          this.$notification.success({
+            message: this.$t('label.edit.account'),
+            description: `${this.$t('message.success.update.account')} 
${params.account}`
+          })
+          this.closeAction()
+        }).catch(error => {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: (error.response && error.response.headers && 
error.response.headers['x-description']) || error.message,
+            duration: 0
+          })
+        }).finally(() => {
+          this.loading = false
+        })
+      }).catch(error => {
+        this.formRef.value.scrollToField(error.errorFields[0].name)
+      })
+    },
+    closeAction () {
+      this.$emit('close-action')
+    }
+  }
+}
+</script>
+<style scoped lang="less">
+  .form-layout {
+    width: 80vw;
+    @media (min-width: 600px) {
+      width: 450px;
+    }
+  }
+</style>
diff --git a/ui/src/views/iam/EditUser.vue b/ui/src/views/iam/EditUser.vue
index e082fd17a06..18f22b6f1e8 100644
--- a/ui/src/views/iam/EditUser.vue
+++ b/ui/src/views/iam/EditUser.vue
@@ -81,6 +81,16 @@
             </a-select-option>
           </a-select>
         </a-form-item>
+        <a-form-item v-if="isRootAdmin" ref="apikeyaccess" name="apikeyaccess">
+          <template #label>
+            <tooltip-label :title="$t('label.apikeyaccess')" 
:tooltip="apiParams.apikeyaccess.description"/>
+          </template>
+          <a-radio-group v-model:value="form.apikeyaccess" buttonStyle="solid">
+            <a-radio-button value="ENABLED">Enabled</a-radio-button>
+            <a-radio-button value="INHERIT">Inherit</a-radio-button>
+            <a-radio-button value="DISABLED">Disabled</a-radio-button>
+          </a-radio-group>
+        </a-form-item>
         <div :span="24" class="action-button">
           <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
           <a-button :loading="loading" ref="submit" type="primary" 
@click="handleSubmit">{{ $t('label.ok') }}</a-button>
@@ -128,6 +138,11 @@ export default {
     this.initForm()
     this.fetchData()
   },
+  computed: {
+    isRootAdmin () {
+      return this.$store.getters.userInfo?.roletype === 'Admin'
+    }
+  },
   methods: {
     initForm () {
       this.formRef = ref()
@@ -187,7 +202,8 @@ export default {
           username: values.username,
           email: values.email,
           firstname: values.firstname,
-          lastname: values.lastname
+          lastname: values.lastname,
+          apikeyaccess: values.apikeyaccess
         }
         if (this.isValidValueForKey(values, 'timezone') && 
values.timezone.length > 0) {
           params.timezone = values.timezone

Reply via email to