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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5d81574  Allow users to share templates with Accounts or Projects 
through the UI
5d81574 is described below

commit 5d8157422dbbcacc66543185544c00eb2bb49c3a
Author: Rohit Yadav <rohit.ya...@shapeblue.com>
AuthorDate: Thu Jul 18 22:12:55 2019 +0530

    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
---
 .../api/BaseUpdateTemplateOrIsoPermissionsCmd.java |   3 +-
 .../command/user/config/ListCapabilitiesCmd.java   |   1 +
 .../api/response/CapabilitiesResponse.java         |   8 +
 .../org/apache/cloudstack/query/QueryService.java  |   4 +
 .../main/java/com/cloud/api/ApiResponseHelper.java |   5 +
 .../java/com/cloud/api/query/QueryManagerImpl.java |  13 +-
 .../com/cloud/api/query/dao/UserVmJoinDaoImpl.java |   6 +-
 .../com/cloud/server/ManagementServerImpl.java     |   7 +-
 .../com/cloud/template/TemplateManagerImpl.java    |  74 ++---
 ui/css/cloudstack3.css                             |   8 +
 ui/l10n/en.js                                      |   5 +
 ui/scripts/cloudStack.js                           |   2 +
 ui/scripts/docs.js                                 |  22 +-
 ui/scripts/sharedFunctions.js                      |   1 +
 ui/scripts/templates.js                            | 319 ++++++++++++++++++++-
 15 files changed, 424 insertions(+), 54 deletions(-)

diff --git 
a/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
index 77e5a15..410ffef 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
@@ -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;
     }
 
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
index 9c52656..40d1a71 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
@@ -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"));
         }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
 
b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
index bcdad46..153d7df 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
@@ -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;
+    }
 }
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java 
b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
index 618a8f6..b9010cb 100644
--- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java
+++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
@@ -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);
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java 
b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 524e109..20bfb96 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -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 {
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java 
b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index 92a110b..ee56cbb 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -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};
     }
 }
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
index 58b167f..0e73743 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -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());
             }
         }
 
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java 
b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 0066a95..305c0f1 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -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);
diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java 
b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
index 373735c..8d732cb 100755
--- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
+++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
@@ -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;
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 76ba97c..a40e252 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -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;
 }
diff --git a/ui/l10n/en.js b/ui/l10n/en.js
index 6a4bba9..53e9814 100644
--- a/ui/l10n/en.js
+++ b/ui/l10n/en.js
@@ -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",
diff --git a/ui/scripts/cloudStack.js b/ui/scripts/cloudStack.js
index 8785cd1..5280e7e 100644
--- a/ui/scripts/cloudStack.js
+++ b/ui/scripts/cloudStack.js
@@ -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
diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js
index ec0b32a..4d00c83 100755
--- a/ui/scripts/docs.js
+++ b/ui/scripts/docs.js
@@ -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.'
     }
 };
diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js
index 9fe5151..84e233f 100644
--- a/ui/scripts/sharedFunctions.js
+++ b/ui/scripts/sharedFunctions.js
@@ -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;
diff --git a/ui/scripts/templates.js b/ui/scripts/templates.js
old mode 100755
new mode 100644
index c64efc9..a05e001
--- a/ui/scripts/templates.js
+++ b/ui/scripts/templates.js
@@ -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"

Reply via email to