mirror of https://github.com/apache/cloudstack.git
Allow users to share templates with Accounts or Projects through the UI
* Allow users to share templates with Accounts or Projects through the updateTemplate permissions API * Change behaviour to show only supported projects and accounts with update template permissions * Allow admins to see accounts dropdown and only hide lists for users * Don't allow sharing project owned templates as you cannot retrieve them in list api calls
This commit is contained in:
parent
a1a9fb8977
commit
5d8157422d
|
|
@ -45,7 +45,7 @@ public abstract class BaseUpdateTemplateOrIsoPermissionsCmd extends BaseCmd {
|
|||
@Parameter(name = ApiConstants.ACCOUNTS,
|
||||
type = CommandType.LIST,
|
||||
collectionType = CommandType.STRING,
|
||||
description = "a comma delimited list of accounts. If specified, \"op\" parameter has to be passed in.")
|
||||
description = "a comma delimited list of accounts within caller's domain. If specified, \"op\" parameter has to be passed in.")
|
||||
private List<String> accountNames;
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class, required = true, description = "the template ID")
|
||||
|
|
@ -80,7 +80,6 @@ public abstract class BaseUpdateTemplateOrIsoPermissionsCmd extends BaseCmd {
|
|||
if (accountNames != null && projectIds != null) {
|
||||
throw new InvalidParameterValueException("Accounts and projectIds can't be specified together");
|
||||
}
|
||||
|
||||
return accountNames;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ public class ListCapabilitiesCmd extends BaseCmd {
|
|||
response.setKVMSnapshotEnabled((Boolean)capabilities.get("KVMSnapshotEnabled"));
|
||||
response.setAllowUserViewDestroyedVM((Boolean)capabilities.get("allowUserViewDestroyedVM"));
|
||||
response.setAllowUserExpungeRecoverVM((Boolean)capabilities.get("allowUserExpungeRecoverVM"));
|
||||
response.setAllowUserViewAllDomainAccounts((Boolean)capabilities.get("allowUserViewAllDomainAccounts"));
|
||||
if (capabilities.containsKey("apiLimitInterval")) {
|
||||
response.setApiLimitInterval((Integer)capabilities.get("apiLimitInterval"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,10 @@ public class CapabilitiesResponse extends BaseResponse {
|
|||
@Param(description = "true if the user can recover and expunge virtualmachines, false otherwise", since = "4.6.0")
|
||||
private boolean allowUserExpungeRecoverVM;
|
||||
|
||||
@SerializedName("allowuserviewalldomainaccounts")
|
||||
@Param(description = "true if users can see all accounts within the same domain, false otherwise")
|
||||
private boolean allowUserViewAllDomainAccounts;
|
||||
|
||||
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
|
||||
this.securityGroupsEnabled = securityGroupsEnabled;
|
||||
}
|
||||
|
|
@ -143,4 +147,8 @@ public class CapabilitiesResponse extends BaseResponse {
|
|||
public void setAllowUserExpungeRecoverVM(boolean allowUserExpungeRecoverVM) {
|
||||
this.allowUserExpungeRecoverVM = allowUserExpungeRecoverVM;
|
||||
}
|
||||
|
||||
public void setAllowUserViewAllDomainAccounts(boolean allowUserViewAllDomainAccounts) {
|
||||
this.allowUserViewAllDomainAccounts = allowUserViewAllDomainAccounts;
|
||||
}
|
||||
}
|
||||
|
|
@ -103,6 +103,10 @@ public interface QueryService {
|
|||
"network offering, zones), we use the flag to determine if the entities should be sorted ascending (when flag is true) " +
|
||||
"or descending (when flag is false). Within the scope of the config all users see the same result.", true, ConfigKey.Scope.Global);
|
||||
|
||||
public static final ConfigKey<Boolean> AllowUserViewAllDomainAccounts = new ConfigKey<>("Advanced", Boolean.class,
|
||||
"allow.user.view.all.domain.accounts", "false",
|
||||
"Determines whether users can view all user accounts within the same domain", true, ConfigKey.Scope.Domain);
|
||||
|
||||
ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException;
|
||||
|
||||
ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd);
|
||||
|
|
|
|||
|
|
@ -1808,6 +1808,11 @@ public class ApiResponseHelper implements ResponseGenerator {
|
|||
List<String> regularAccounts = new ArrayList<String>();
|
||||
for (String accountName : accountNames) {
|
||||
Account account = ApiDBUtils.findAccountByNameDomain(accountName, templateOwner.getDomainId());
|
||||
if (account == null) {
|
||||
s_logger.error("Missing Account " + accountName + " in domain " + templateOwner.getDomainId());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (account.getType() != Account.ACCOUNT_TYPE_PROJECT) {
|
||||
regularAccounts.add(accountName);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -394,6 +394,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
* com.cloud.api.query.QueryService#searchForUsers(org.apache.cloudstack
|
||||
* .api.command.admin.user.ListUsersCmd)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException {
|
||||
Pair<List<UserAccountJoinVO>, Integer> result = searchForUsersInternal(cmd);
|
||||
|
|
@ -1980,7 +1981,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
// if no "id" specified...
|
||||
if (accountId == null) {
|
||||
// listall only has significance if they are an admin
|
||||
if (listAll && callerIsAdmin) {
|
||||
boolean isDomainListAllAllowed = AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId());
|
||||
if ((listAll && callerIsAdmin) || isDomainListAllAllowed) {
|
||||
// if no domain id specified, use caller's domain
|
||||
if (domainId == null) {
|
||||
domainId = caller.getDomainId();
|
||||
|
|
@ -2026,6 +2028,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
sb.and("needsCleanup", sb.entity().isNeedsCleanup(), SearchCriteria.Op.EQ);
|
||||
sb.and("typeNEQ", sb.entity().getType(), SearchCriteria.Op.NEQ);
|
||||
sb.and("idNEQ", sb.entity().getId(), SearchCriteria.Op.NEQ);
|
||||
sb.and("type2NEQ", sb.entity().getType(), SearchCriteria.Op.NEQ);
|
||||
|
||||
if (domainId != null && isRecursive) {
|
||||
sb.and("path", sb.entity().getDomainPath(), SearchCriteria.Op.LIKE);
|
||||
|
|
@ -2035,9 +2038,15 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
|
||||
// don't return account of type project to the end user
|
||||
sc.setParameters("typeNEQ", Account.ACCOUNT_TYPE_PROJECT);
|
||||
|
||||
// don't return system account...
|
||||
sc.setParameters("idNEQ", Account.ACCOUNT_ID_SYSTEM);
|
||||
|
||||
// do not return account of type domain admin to the end user
|
||||
if (!callerIsAdmin) {
|
||||
sc.setParameters("type2NEQ", Account.ACCOUNT_TYPE_DOMAIN_ADMIN);
|
||||
}
|
||||
|
||||
if (keyword != null) {
|
||||
SearchCriteria<AccountJoinVO> ssc = _accountJoinDao.createSearchCriteria();
|
||||
ssc.addOr("accountName", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
|
|
@ -3836,6 +3845,6 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {AllowUserViewDestroyedVM, UserVMBlacklistedDetails, UserVMReadOnlyUIDetails, SortKeyAscending};
|
||||
return new ConfigKey<?>[] {AllowUserViewDestroyedVM, UserVMBlacklistedDetails, UserVMReadOnlyUIDetails, SortKeyAscending, AllowUserViewAllDomainAccounts};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
|
|||
import org.apache.cloudstack.api.response.SecurityGroupResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.query.QueryService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.api.ApiDBUtils;
|
||||
import com.cloud.api.ApiResponseHelper;
|
||||
import com.cloud.api.query.QueryManagerImpl;
|
||||
import com.cloud.api.query.vo.UserVmJoinVO;
|
||||
import com.cloud.gpu.GPU;
|
||||
import com.cloud.service.ServiceOfferingDetailsVO;
|
||||
|
|
@ -315,14 +315,14 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
}
|
||||
// Remove blacklisted settings if user is not admin
|
||||
if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
|
||||
String[] userVmSettingsToHide = QueryManagerImpl.UserVMBlacklistedDetails.value().split(",");
|
||||
String[] userVmSettingsToHide = QueryService.UserVMBlacklistedDetails.value().split(",");
|
||||
for (String key : userVmSettingsToHide) {
|
||||
resourceDetails.remove(key.trim());
|
||||
}
|
||||
}
|
||||
userVmResponse.setDetails(resourceDetails);
|
||||
if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
|
||||
userVmResponse.setReadOnlyUIDetails(QueryManagerImpl.UserVMReadOnlyUIDetails.value());
|
||||
userVmResponse.setReadOnlyUIDetails(QueryService.UserVMReadOnlyUIDetails.value());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -535,6 +535,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
|||
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
|
||||
import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.cloudstack.query.QueryService;
|
||||
import org.apache.cloudstack.resourcedetail.dao.GuestOsDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
|
||||
|
|
@ -555,7 +556,6 @@ import com.cloud.alert.AlertManager;
|
|||
import com.cloud.alert.AlertVO;
|
||||
import com.cloud.alert.dao.AlertDao;
|
||||
import com.cloud.api.ApiDBUtils;
|
||||
import com.cloud.api.query.QueryManagerImpl;
|
||||
import com.cloud.capacity.Capacity;
|
||||
import com.cloud.capacity.CapacityVO;
|
||||
import com.cloud.capacity.dao.CapacityDao;
|
||||
|
|
@ -3486,9 +3486,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
|||
final Integer apiLimitInterval = Integer.valueOf(_configDao.getValue(Config.ApiLimitInterval.key()));
|
||||
final Integer apiLimitMax = Integer.valueOf(_configDao.getValue(Config.ApiLimitMax.key()));
|
||||
|
||||
final boolean allowUserViewDestroyedVM = (QueryManagerImpl.AllowUserViewDestroyedVM.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
|
||||
final boolean allowUserViewDestroyedVM = (QueryService.AllowUserViewDestroyedVM.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
|
||||
final boolean allowUserExpungeRecoverVM = (UserVmManager.AllowUserExpungeRecoverVm.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
|
||||
|
||||
final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId()));
|
||||
|
||||
// check if region-wide secondary storage is used
|
||||
boolean regionSecondaryEnabled = false;
|
||||
final List<ImageStoreVO> imgStores = _imgStoreDao.findRegionImageStores();
|
||||
|
|
@ -3508,6 +3510,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
|||
capabilities.put("KVMSnapshotEnabled", KVMSnapshotEnabled);
|
||||
capabilities.put("allowUserViewDestroyedVM", allowUserViewDestroyedVM);
|
||||
capabilities.put("allowUserExpungeRecoverVM", allowUserExpungeRecoverVM);
|
||||
capabilities.put("allowUserViewAllDomainAccounts", allowUserViewAllDomainAccounts);
|
||||
if (apiLimitEnabled) {
|
||||
capabilities.put("apiLimitInterval", apiLimitInterval);
|
||||
capabilities.put("apiLimitMax", apiLimitMax);
|
||||
|
|
|
|||
|
|
@ -32,28 +32,6 @@ import java.util.concurrent.Executors;
|
|||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.deploy.DeployDestination;
|
||||
import com.cloud.storage.ImageStoreUploadMonitorImpl;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.EncryptionUtil;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.EnumUtils;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd;
|
||||
import org.apache.cloudstack.framework.async.AsyncCallFuture;
|
||||
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
|
||||
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseListTemplateOrIsoPermissionsCmd;
|
||||
|
|
@ -61,6 +39,7 @@ import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd;
|
|||
import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoPermissionsCmd;
|
||||
import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd;
|
||||
import org.apache.cloudstack.api.command.user.iso.ExtractIsoCmd;
|
||||
import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd;
|
||||
import org.apache.cloudstack.api.command.user.iso.ListIsoPermissionsCmd;
|
||||
import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd;
|
||||
import org.apache.cloudstack.api.command.user.iso.UpdateIsoCmd;
|
||||
|
|
@ -69,6 +48,7 @@ import org.apache.cloudstack.api.command.user.template.CopyTemplateCmd;
|
|||
import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.ListTemplatePermissionsCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.UpdateTemplateCmd;
|
||||
|
|
@ -95,6 +75,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.Templa
|
|||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
|
||||
import org.apache.cloudstack.framework.async.AsyncCallFuture;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
|
|
@ -104,6 +85,9 @@ import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
|||
import org.apache.cloudstack.storage.command.AttachCommand;
|
||||
import org.apache.cloudstack.storage.command.CommandResult;
|
||||
import org.apache.cloudstack.storage.command.DettachCommand;
|
||||
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
|
|
@ -111,6 +95,12 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
|||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
|
||||
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
|
@ -129,6 +119,7 @@ import com.cloud.configuration.Resource.ResourceType;
|
|||
import com.cloud.dc.DataCenter;
|
||||
import com.cloud.dc.DataCenterVO;
|
||||
import com.cloud.dc.dao.DataCenterDao;
|
||||
import com.cloud.deploy.DeployDestination;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.event.ActionEvent;
|
||||
|
|
@ -148,6 +139,7 @@ import com.cloud.projects.Project;
|
|||
import com.cloud.projects.ProjectManager;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.GuestOSVO;
|
||||
import com.cloud.storage.ImageStoreUploadMonitorImpl;
|
||||
import com.cloud.storage.LaunchPermissionVO;
|
||||
import com.cloud.storage.Snapshot;
|
||||
import com.cloud.storage.SnapshotVO;
|
||||
|
|
@ -186,6 +178,11 @@ import com.cloud.user.AccountVO;
|
|||
import com.cloud.user.ResourceLimitService;
|
||||
import com.cloud.user.dao.AccountDao;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.EncryptionUtil;
|
||||
import com.cloud.utils.EnumUtils;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
|
|
@ -200,11 +197,12 @@ import com.cloud.vm.UserVmVO;
|
|||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachine.State;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.dao.UserVmDao;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class TemplateManagerImpl extends ManagerBase implements TemplateManager, TemplateApiService, Configurable {
|
||||
private final static Logger s_logger = Logger.getLogger(TemplateManagerImpl.class);
|
||||
|
|
@ -1483,9 +1481,24 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
throw new InvalidParameterValueException("unable to update permissions for " + mediaType + " with id " + id);
|
||||
}
|
||||
|
||||
boolean isAdmin = _accountMgr.isAdmin(caller.getId());
|
||||
Long ownerId = template.getAccountId();
|
||||
Account owner = _accountMgr.getAccount(ownerId);
|
||||
if (ownerId == null) {
|
||||
// if there is no owner of the template then it's probably already a
|
||||
// public template (or domain private template) so
|
||||
// publishing to individual users is irrelevant
|
||||
throw new InvalidParameterValueException("Update template permissions is an invalid operation on template " + template.getName());
|
||||
}
|
||||
|
||||
if (owner.getType() == Account.ACCOUNT_TYPE_PROJECT) {
|
||||
// Currently project owned templates cannot be shared outside project but is available to all users within project by default.
|
||||
throw new InvalidParameterValueException("Update template permissions is an invalid operation on template " + template.getName() +
|
||||
". Project owned templates cannot be shared outside template.");
|
||||
}
|
||||
|
||||
// check configuration parameter(allow.public.user.templates) value for
|
||||
// the template owner
|
||||
boolean isAdmin = _accountMgr.isAdmin(caller.getId());
|
||||
boolean allowPublicUserTemplates = AllowPublicUserTemplates.valueIn(template.getAccountId());
|
||||
if (!isAdmin && !allowPublicUserTemplates && isPublic != null && isPublic) {
|
||||
throw new InvalidParameterValueException("Only private " + mediaType + "s can be created.");
|
||||
|
|
@ -1499,14 +1512,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
}
|
||||
}
|
||||
|
||||
Long ownerId = template.getAccountId();
|
||||
if (ownerId == null) {
|
||||
// if there is no owner of the template then it's probably already a
|
||||
// public template (or domain private template) so
|
||||
// publishing to individual users is irrelevant
|
||||
throw new InvalidParameterValueException("Update template permissions is an invalid operation on template " + template.getName());
|
||||
}
|
||||
|
||||
//Only admin or owner of the template should be able to change its permissions
|
||||
if (caller.getId() != ownerId && !isAdmin) {
|
||||
throw new InvalidParameterValueException("Unable to grant permission to account " + caller.getAccountName() + " as it is neither admin nor owner or the template");
|
||||
|
|
@ -1540,7 +1545,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
}
|
||||
|
||||
//Derive the domain id from the template owner as updateTemplatePermissions is not cross domain operation
|
||||
Account owner = _accountMgr.getAccount(ownerId);
|
||||
final Domain domain = _domainDao.findById(owner.getDomainId());
|
||||
if ("add".equalsIgnoreCase(operation)) {
|
||||
final List<String> accountNamesFinal = accountNames;
|
||||
|
|
|
|||
|
|
@ -12421,6 +12421,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
|
|||
background-position: -35px -707px;
|
||||
}
|
||||
|
||||
.shareTemplate .icon {
|
||||
background-position: -165px -122px;
|
||||
}
|
||||
|
||||
.shareTemplate:hover .icon {
|
||||
background-position: -165px -704px;
|
||||
}
|
||||
|
||||
.createVolume .icon {
|
||||
background-position: -70px -124px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ var dictionary = {
|
|||
"label.about.app":"About CloudStack",
|
||||
"label.accept.project.invitation":"Accept project invitation",
|
||||
"label.account":"Account",
|
||||
"label.accounts":"Accounts",
|
||||
"label.account.and.security.group":"Account, Security group",
|
||||
"label.account.details":"Account details",
|
||||
"label.account.id":"Account ID",
|
||||
|
|
@ -279,6 +280,7 @@ var dictionary = {
|
|||
"label.action.run.diagnostics":"Run Diagnostics",
|
||||
"label.action.secure.host":"Provision Host Security Keys",
|
||||
"label.action.start.instance":"Start Instance",
|
||||
"label.action.share.template": "Update Template Permissions",
|
||||
"label.action.start.instance.processing":"Starting Instance....",
|
||||
"label.action.start.router":"Start Router",
|
||||
"label.action.start.router.processing":"Starting Router....",
|
||||
|
|
@ -1253,6 +1255,7 @@ var dictionary = {
|
|||
"label.opendaylight.controller":"OpenDaylight Controller",
|
||||
"label.opendaylight.controllerdetail":"OpenDaylight Controller Details",
|
||||
"label.opendaylight.controllers":"OpenDaylight Controllers",
|
||||
"label.operation": "Operation",
|
||||
"label.operator":"Operator",
|
||||
"label.optional":"Optional",
|
||||
"label.order":"Order",
|
||||
|
|
@ -1342,6 +1345,7 @@ var dictionary = {
|
|||
"label.project":"Project",
|
||||
"label.project.dashboard":"Project dashboard",
|
||||
"label.project.id":"Project ID",
|
||||
"label.project.ids":"Project IDs",
|
||||
"label.project.invite":"Invite to project",
|
||||
"label.project.name":"Project name",
|
||||
"label.project.view":"Project View",
|
||||
|
|
@ -1576,6 +1580,7 @@ var dictionary = {
|
|||
"label.setup.network":"Set up Network",
|
||||
"label.setup.zone":"Set up Zone",
|
||||
"label.shared":"Shared",
|
||||
"label.share.with":"Share With",
|
||||
"label.show.advanced.settings":"Show advanced settings",
|
||||
"label.show.ingress.rule":"Show Ingress Rule",
|
||||
"label.shutdown.provider":"Shutdown provider",
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@
|
|||
g_userProjectsEnabled = json.listcapabilitiesresponse.capability.allowusercreateprojects;
|
||||
|
||||
g_cloudstackversion = json.listcapabilitiesresponse.capability.cloudstackversion;
|
||||
// Allow users to see all accounts within a domain
|
||||
g_allowUserViewAllDomainAccounts = json.listcapabilitiesresponse.capability.allowuserviewalldomainaccounts;
|
||||
|
||||
if (json.listcapabilitiesresponse.capability.apilimitinterval != null && json.listcapabilitiesresponse.capability.apilimitmax != null) {
|
||||
var intervalLimit = ((json.listcapabilitiesresponse.capability.apilimitinterval * 1000) / json.listcapabilitiesresponse.capability.apilimitmax) * 3; //multiply 3 to be on safe side
|
||||
|
|
|
|||
|
|
@ -1368,29 +1368,41 @@ cloudStack.docs = {
|
|||
desc: 'Pass user and meta data to VMs (via ConfigDrive)',
|
||||
externalLink: ''
|
||||
},
|
||||
|
||||
helpComputeOfferingMinCPUCores: {
|
||||
desc: 'This will be used for the setting the range (min-max) of the number of cpu cores that should be allowed for VMs using this custom offering.',
|
||||
externalLink: ''
|
||||
},
|
||||
|
||||
helpComputeOfferingMaxCPUCores: {
|
||||
desc: 'This will be used for the setting the range (min-max) of the number of cpu cores that should be allowed for VMs using this custom offering.',
|
||||
externalLink: ''
|
||||
},
|
||||
|
||||
helpComputeOfferingMinMemory: {
|
||||
desc: 'This will be used for the setting the range (min-max) amount of memory that should be allowed for VMs using this custom offering.',
|
||||
externalLink: ''
|
||||
},
|
||||
|
||||
helpComputeOfferingMaxMemory: {
|
||||
desc: 'This will be used for the setting the range (min-max) amount of memory that should be allowed for VMs using this custom offering.',
|
||||
externalLink: ''
|
||||
},
|
||||
|
||||
helpComputeOfferingType: {
|
||||
desc: 'This will be used for setting the type of compute offering - whether it is fixed, custom constrained or custom unconstrained.',
|
||||
externalLink: ''
|
||||
},
|
||||
|
||||
// Update Template Permissions Helper
|
||||
helpUpdateTemplateOperation: {
|
||||
desc: 'Select the permission operator. Add is for sharing with user/project and Reset simply removes all the accounts and projects which template has been shared with.'
|
||||
},
|
||||
helpUpdateTemplateShareWith: {
|
||||
desc: 'Select account or project with which template is to be shared with.'
|
||||
},
|
||||
helpUpdateTemplateAccounts: {
|
||||
desc: 'Choose one or more accounts to share this template. Ctrl+Click to select multiple accounts to share with. Selecting "Add > Accounts" shows list of accounts that do not have permissions. Selecting "Remove > Accounts" shows list of accounts that already have permissions.'
|
||||
},
|
||||
helpUpdateTemplateProjectIds: {
|
||||
desc: 'Choose one or more projects to share this template. Ctrl+Click to select multiple projects to share with. Selecting "Add > Projects" shows list of projects that do not have permissions. Selecting "Remove > Projects" shows list of projects that already have permissions.'
|
||||
},
|
||||
helpUpdateTemplateAccountList: {
|
||||
desc: 'A comma seperated list of accounts to share the template with. Must be specified with the Add/Remove operation, leave Project ID blank if this is specified.'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ var g_queryAsyncJobResultInterval = 3000;
|
|||
var g_idpList = null;
|
||||
var g_appendIdpDomain = false;
|
||||
var g_sortKeyIsAscending = false;
|
||||
var g_allowUserViewAllDomainAccounts= false;
|
||||
|
||||
//keyboard keycode
|
||||
var keycode_Enter = 13;
|
||||
|
|
|
|||
|
|
@ -1507,8 +1507,316 @@
|
|||
notification: {
|
||||
poll: pollAsyncJobResult
|
||||
}
|
||||
}
|
||||
},
|
||||
// Share template
|
||||
shareTemplate: {
|
||||
label: 'label.action.share.template',
|
||||
messages: {
|
||||
notification: function (args) {
|
||||
return 'label.action.share.template';
|
||||
}
|
||||
},
|
||||
|
||||
createForm: {
|
||||
title: 'label.action.share.template',
|
||||
desc: '',
|
||||
fields: {
|
||||
operation: {
|
||||
label: 'label.operation',
|
||||
docID: 'helpUpdateTemplateOperation',
|
||||
validation: {
|
||||
required: true
|
||||
},
|
||||
select: function (args) {
|
||||
var items = [];
|
||||
items.push({
|
||||
id: "add",
|
||||
description: "Add"
|
||||
});
|
||||
items.push({
|
||||
id: "remove",
|
||||
description: "Remove"
|
||||
});
|
||||
items.push({
|
||||
id: "reset",
|
||||
description: "Reset"
|
||||
});
|
||||
|
||||
args.response.success({
|
||||
data: items
|
||||
});
|
||||
|
||||
// Select change
|
||||
args.$select.change(function () {
|
||||
var $form = $(this).closest('form');
|
||||
var selectedOperation = $(this).val();
|
||||
if (selectedOperation === "reset") {
|
||||
$form.find('[rel=projects]').hide();
|
||||
$form.find('[rel=sharewith]').hide();
|
||||
$form.find('[rel=accounts]').hide();
|
||||
$form.find('[rel=accountlist]').hide();
|
||||
} else {
|
||||
// allow.user.view.domain.accounts = true
|
||||
// Populate List of accounts in domain as dropdown multiselect
|
||||
$form.find('[rel=sharewith]').css('display', 'inline-block');
|
||||
if (!isUser() || g_allowUserViewAllDomainAccounts === true) {
|
||||
$form.find('[rel=projects]').css('display', 'inline-block');
|
||||
$form.find('[rel=accounts]').css('display', 'inline-block');
|
||||
$form.find('[rel=accountlist]').hide();
|
||||
} else {
|
||||
// If users are not allowed to see accounts in the domain, show input text field for Accounts
|
||||
// Projects will always be shown as dropdown multiselect
|
||||
$form.find('[rel=projects]').css('display', 'inline-block');
|
||||
$form.find('[rel=accountslist]').css('display', 'inline-block');
|
||||
$form.find('[rel=accounts]').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
shareWith: {
|
||||
label: 'label.share.with',
|
||||
docID: 'helpUpdateTemplateShareWith',
|
||||
validation: {
|
||||
required: true
|
||||
},
|
||||
dependsOn: 'operation',
|
||||
select: function (args) {
|
||||
var items = [];
|
||||
items.push({
|
||||
id: "account",
|
||||
description: "Account"
|
||||
});
|
||||
items.push({
|
||||
id: "project",
|
||||
description: "Project"
|
||||
});
|
||||
|
||||
args.response.success({ data: items });
|
||||
|
||||
// Select change
|
||||
args.$select.change(function () {
|
||||
var $form = $(this).closest('form');
|
||||
var sharedWith = $(this).val();
|
||||
if (args.operation !== "reset") {
|
||||
if (sharedWith === "project") {
|
||||
$form.find('[rel=accounts]').hide();
|
||||
$form.find('[rel=accountlist]').hide();
|
||||
$form.find('[rel=projects]').css('display', 'inline-block');
|
||||
} else {
|
||||
// allow.user.view.domain.accounts = true
|
||||
// Populate List of accounts in domain as dropdown multiselect
|
||||
if (!isUser() || g_allowUserViewAllDomainAccounts === true) {
|
||||
$form.find('[rel=projects]').hide();
|
||||
$form.find('[rel=accountlist]').hide();
|
||||
$form.find('[rel=accounts]').css('display', 'inline-block');
|
||||
} else {
|
||||
// If users are not allowed to see accounts in the domain, show input text field for Accounts
|
||||
// Projects will always be shown as dropdown multiselect
|
||||
$form.find('[rel=projects]').hide();
|
||||
$form.find('[rel=accounts]').hide();
|
||||
$form.find('[rel=accountlist]').css('display', 'inline-block');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
accountlist: {
|
||||
label: 'label.accounts',
|
||||
docID: 'helpUpdateTemplateAccountList'
|
||||
},
|
||||
|
||||
accounts: {
|
||||
label: 'label.accounts',
|
||||
docID: 'helpUpdateTemplateAccounts',
|
||||
dependsOn: 'shareWith',
|
||||
isMultiple: true,
|
||||
select: function (args) {
|
||||
var operation = args.operation;
|
||||
if (operation !== "reset") {
|
||||
$.ajax({
|
||||
url: createURL("listAccounts&listall=true"),
|
||||
dataType: "json",
|
||||
async: true,
|
||||
success: function (jsonAccounts) {
|
||||
var accountByName = {};
|
||||
$.each(jsonAccounts.listaccountsresponse.account, function(idx, account) {
|
||||
// Only add current domain's accounts as update template permissions supports that
|
||||
if (account.domainid === g_domainid) {
|
||||
accountByName[account.name] = {
|
||||
projName: account.name,
|
||||
hasPermission: false
|
||||
};
|
||||
}
|
||||
});
|
||||
$.ajax({
|
||||
url: createURL('listTemplatePermissions&id=' + args.context.templates[0].id),
|
||||
dataType: "json",
|
||||
async: true,
|
||||
success: function (json) {
|
||||
items = json.listtemplatepermissionsresponse.templatepermission.account;
|
||||
$.each(items, function(idx, accountName) {
|
||||
accountByName[accountName].hasPermission = true;
|
||||
});
|
||||
|
||||
var accountObjs = [];
|
||||
if (operation === "add") {
|
||||
// Skip already permitted accounts
|
||||
$.each(Object.keys(accountByName), function(idx, accountName) {
|
||||
if (accountByName[accountName].hasPermission == false) {
|
||||
accountObjs.push({
|
||||
name: accountName,
|
||||
description: accountName
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (items != null) {
|
||||
$.each(items, function(idx, accountName) {
|
||||
if (accountName !== g_account) {
|
||||
accountObjs.push({
|
||||
name: accountName,
|
||||
description: accountName
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
args.$select.html('');
|
||||
args.response.success({data: accountObjs});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
projects: {
|
||||
label: 'label.projects',
|
||||
docID: 'helpUpdateTemplateProjectIds',
|
||||
dependsOn: 'shareWith',
|
||||
isMultiple: true,
|
||||
select: function (args) {
|
||||
var operation = args.operation;
|
||||
if (operation !== "reset") {
|
||||
$.ajax({
|
||||
url: createURL("listProjects&listall=true"),
|
||||
dataType: "json",
|
||||
async: true,
|
||||
success: function (jsonProjects) {
|
||||
var projectsByIds = {};
|
||||
$.each(jsonProjects.listprojectsresponse.project, function(idx, project) {
|
||||
// Only add current domain's projects as update template permissions supports that
|
||||
if (project.domainid === g_domainid) {
|
||||
projectsByIds[project.id] = {
|
||||
projName: project.name,
|
||||
hasPermission: false
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: createURL('listTemplatePermissions&id=' + args.context.templates[0].id),
|
||||
dataType: "json",
|
||||
async: true,
|
||||
success: function (json) {
|
||||
items = json.listtemplatepermissionsresponse.templatepermission.projectids;
|
||||
$.each(items, function(idx, projectId) {
|
||||
projectsByIds[projectId].hasPermission = true;
|
||||
});
|
||||
|
||||
var projectObjs = [];
|
||||
if (operation === "add") {
|
||||
// Skip already permitted accounts
|
||||
$.each(Object.keys(projectsByIds), function(idx, projectId) {
|
||||
if (projectsByIds[projectId].hasPermission == false) {
|
||||
projectObjs.push({
|
||||
id: projectId,
|
||||
description: projectsByIds[projectId].projName
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (items != null) {
|
||||
$.each(items, function(idx, projectId) {
|
||||
if (projectId !== g_account) {
|
||||
projectObjs.push({
|
||||
id: projectId,
|
||||
description: projectsByIds[projectId] ? projectsByIds[projectId].projName : projectId
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
args.$select.html('');
|
||||
args.response.success({data: projectObjs});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
action: function (args) {
|
||||
// Load data from form
|
||||
var data = {
|
||||
id: args.context.templates[0].id,
|
||||
op: args.data.operation
|
||||
};
|
||||
var selectedOperation = args.data.operation;
|
||||
if (selectedOperation === "reset") {
|
||||
// Do not append Project ID or Account to data object
|
||||
} else {
|
||||
var projects = args.data.projects;
|
||||
var accounts = args.data.accounts;
|
||||
var accountList = args.data.accountlist;
|
||||
|
||||
if (accounts !== undefined || (accountList !== undefined && accountList.length > 0)) {
|
||||
var accountNames = "";
|
||||
if (accountList !== undefined && accounts === undefined) {
|
||||
accountNames = accountList;
|
||||
} else {
|
||||
if (Object.prototype.toString.call(accounts) === '[object Array]') {
|
||||
accountNames = accounts.join(",");
|
||||
} else {
|
||||
accountNames = accounts;
|
||||
}
|
||||
}
|
||||
$.extend(data, {
|
||||
accounts: accountNames
|
||||
});
|
||||
}
|
||||
|
||||
if (projects !== undefined) {
|
||||
var projectIds = "";
|
||||
if (Object.prototype.toString.call(projects) === '[object Array]') {
|
||||
projectIds = projects.join(",");
|
||||
} else {
|
||||
projectIds = projects;
|
||||
}
|
||||
|
||||
$.extend(data, {
|
||||
projectids: projectIds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: createURL('updateTemplatePermissions'),
|
||||
data: data,
|
||||
dataType: "json",
|
||||
async: false,
|
||||
success: function (json) {
|
||||
var item = json.updatetemplatepermissionsresponse.success;
|
||||
args.response.success({
|
||||
data: item
|
||||
});
|
||||
}
|
||||
}); //end ajax
|
||||
}
|
||||
}
|
||||
},
|
||||
tabs: {
|
||||
details: {
|
||||
|
|
@ -1882,11 +2190,11 @@
|
|||
}else if(args.page == 1) {
|
||||
args.response.success({
|
||||
data: []
|
||||
});
|
||||
});
|
||||
} else {
|
||||
args.response.success({
|
||||
data: []
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -2202,7 +2510,7 @@
|
|||
}
|
||||
}
|
||||
newDetails += 'details[0].' + data.name + '=' + data.value;
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: createURL('updateTemplate&id=' + args.context.templates[0].id + '&' + newDetails),
|
||||
success: function(json) {
|
||||
|
|
@ -3429,7 +3737,7 @@
|
|||
allowedActions.push("copyTemplate");
|
||||
}
|
||||
|
||||
// "Download Template"
|
||||
// "Download Template" , "Update Template Permissions"
|
||||
if (((isAdmin() == false && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account) && !(jsonObj.domainid == g_domainid && cloudStack.context.projects && jsonObj.projectid == cloudStack.context.projects[0].id))) //if neither root-admin, nor the same account, nor the same project
|
||||
|| (jsonObj.isready == false) || jsonObj.templatetype == "SYSTEM") {
|
||||
//do nothing
|
||||
|
|
@ -3437,6 +3745,7 @@
|
|||
if (jsonObj.isextractable){
|
||||
allowedActions.push("downloadTemplate");
|
||||
}
|
||||
allowedActions.push("shareTemplate");
|
||||
}
|
||||
|
||||
// "Delete Template"
|
||||
|
|
|
|||
Loading…
Reference in New Issue