mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
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 <lucas.martins@scclouds.com.br> * 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 <daan@onecht.net> Co-authored-by: Lucas Martins <56271185+lucas-a-martins@users.noreply.github.com> Co-authored-by: Lucas Martins <lucas.martins@scclouds.com.br>
This commit is contained in:
parent
58138f2da3
commit
d17de834a5
@ -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";
|
||||
|
||||
@ -93,4 +93,8 @@ public interface Account extends ControlledEntity, InternalIdentity, Identity {
|
||||
|
||||
boolean isDefault();
|
||||
|
||||
public void setApiKeyAccess(Boolean apiKeyAccess);
|
||||
|
||||
public Boolean getApiKeyAccess();
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -94,4 +94,9 @@ public interface User extends OwnedBy, InternalIdentity {
|
||||
public boolean isUser2faEnabled();
|
||||
|
||||
public String getKeyFor2fa();
|
||||
|
||||
public void setApiKeyAccess(Boolean apiKeyAccess);
|
||||
|
||||
public Boolean getApiKeyAccess();
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
boolean apiKeyAccess = rs.getBoolean(6);
|
||||
if (rs.wasNull()) {
|
||||
u.setApiKeyAccess(null);
|
||||
} else {
|
||||
u.setApiKeyAccess(apiKeyAccess);
|
||||
}
|
||||
|
||||
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)));
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
--;
|
||||
@ -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" ');
|
||||
@ -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`,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 () {
|
||||
|
||||
@ -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 = []
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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: [
|
||||
|
||||
190
ui/src/views/iam/EditAccount.vue
Normal file
190
ui/src/views/iam/EditAccount.vue
Normal file
@ -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>
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user