diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index daa843f8954..ace0d20cfee 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -29,6 +29,7 @@ import com.cloud.dc.DataCenter; import com.cloud.domain.Domain; import com.cloud.exception.PermissionDeniedException; import com.cloud.offering.DiskOffering; +import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; public interface AccountService { @@ -101,6 +102,8 @@ public interface AccountService { void checkAccess(Account account, DiskOffering dof, DataCenter zone) throws PermissionDeniedException; + void checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException; + void checkAccess(User user, ControlledEntity entity); void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) throws PermissionDeniedException; diff --git a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java index b1f95b3399f..ca80e69cc37 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java @@ -20,6 +20,7 @@ import com.cloud.dc.DataCenter; import com.cloud.domain.Domain; import com.cloud.exception.PermissionDeniedException; import com.cloud.offering.DiskOffering; +import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.user.Account; import com.cloud.user.User; @@ -139,4 +140,6 @@ public interface SecurityChecker extends Adapter { boolean checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException; boolean checkAccess(Account account, DiskOffering dof, DataCenter zone) throws PermissionDeniedException; + + boolean checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException; } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index d802485352e..691bbb61131 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -171,6 +171,7 @@ import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.user.Account; +import com.cloud.user.AccountManager; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; @@ -286,6 +287,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra RemoteAccessVpnDao _remoteAccessVpnDao; @Inject VpcVirtualNetworkApplianceService _routerService; + @Inject + AccountManager accountManager; List networkGurus; @@ -2162,6 +2165,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { final NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId); + final DataCenterVO zone = _dcDao.findById(zoneId); + accountManager.checkAccess(owner, ntwkOff, zone); // this method supports only guest network creation if (ntwkOff.getTrafficType() != TrafficType.Guest) { s_logger.warn("Only guest networks can be created using this method"); @@ -2196,7 +2201,6 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra ipv6 = true; } // Validate zone - final DataCenterVO zone = _dcDao.findById(zoneId); if (zone.getNetworkType() == NetworkType.Basic) { // In Basic zone the network should have aclType=Domain, domainId=1, subdomainAccess=true if (aclType == null || aclType != ACLType.Domain) { diff --git a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java index a74c66cc9f8..545371afeff 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java @@ -54,11 +54,11 @@ public class NetworkOfferingDetailsVO implements ResourceDetail { public NetworkOfferingDetailsVO() { } - public NetworkOfferingDetailsVO(long resourceId, Detail detailName, String value) { + public NetworkOfferingDetailsVO(long resourceId, Detail detailName, String value, boolean display) { this.resourceId = resourceId; this.name = detailName; this.value = value; - this.display = false; + this.display = display; } @Override diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java index dd607edcac7..93fe2480bbe 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java @@ -23,7 +23,6 @@ import java.util.Map; import javax.inject.Inject; import javax.persistence.EntityExistsException; -import com.cloud.offerings.NetworkOfferingServiceMapVO; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; @@ -33,6 +32,7 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offering.NetworkOffering.Detail; import com.cloud.offerings.NetworkOfferingDetailsVO; +import com.cloud.offerings.NetworkOfferingServiceMapVO; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; @@ -187,7 +187,7 @@ public class NetworkOfferingDaoImpl extends GenericDaoBase getNtwkOffDetails(long offeringId) { SearchCriteria sc = DetailSearch.create(); sc.setParameters("resourceId", offeringId); + sc.setParameters("display", true); List results = search(sc, null); Map details = new HashMap(results.size()); @@ -81,7 +84,7 @@ public class NetworkOfferingDetailsDaoImpl extends ResourceDetailsDaoBase doDomainIds = networkserviceOfferingDetailsDao.findDomainIds(nof.getId()); + if (doDomainIds.isEmpty()) { + isAccess = true; + } else { + for (Long domainId : doDomainIds) { + if (_domainDao.isChildDomain(domainId, account.getDomainId())) { + isAccess = true; + break; + } + } + } + } + } + // Check for zones + if (isAccess && nof != null && zone != null) { + final List doZoneIds = networkserviceOfferingDetailsDao.findZoneIds(nof.getId()); + isAccess = doZoneIds.isEmpty() || doZoneIds.contains(zone.getId()); + } + return isAccess; + } + @Override public boolean checkAccess(Account account, DataCenter zone) throws PermissionDeniedException { if (account == null || zone.getDomainId() == null) {//public zone diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index a3c217a6ead..2571596f10d 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -175,6 +175,7 @@ import com.cloud.api.query.vo.EventJoinVO; import com.cloud.api.query.vo.HostJoinVO; import com.cloud.api.query.vo.ImageStoreJoinVO; import com.cloud.api.query.vo.InstanceGroupJoinVO; +import com.cloud.api.query.vo.NetworkOfferingJoinVO; import com.cloud.api.query.vo.ProjectAccountJoinVO; import com.cloud.api.query.vo.ProjectInvitationJoinVO; import com.cloud.api.query.vo.ProjectJoinVO; @@ -1913,6 +1914,9 @@ public class ApiResponseHelper implements ResponseGenerator { @Override public NetworkOfferingResponse createNetworkOfferingResponse(NetworkOffering offering) { + if (!(offering instanceof NetworkOfferingJoinVO)) { + offering = ApiDBUtils.newNetworkOfferingView(offering); + } NetworkOfferingResponse response = ApiDBUtils.newNetworkOfferingResponse(offering); response.setNetworkRate(ApiDBUtils.getNetworkRate(offering.getId())); Long so = null; diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index a19e15b80f7..db8cb062760 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -541,7 +541,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } if(name.equals(CapacityManager.StorageOverprovisioningFactor.key())) { if(!pool.getPoolType().supportsOverProvisioning() ) { - throw new InvalidParameterValueException("Unable to update storage pool with id " + resourceId + ". Overprovision not supported for " + pool.getPoolType()); + throw new InvalidParameterValueException("Unable to update storage pool with id " + resourceId + ". Overprovision not supported for " + pool.getPoolType()); } } @@ -2631,12 +2631,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } for (Long domainId : existingDomainIds) { if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) { - throw new InvalidParameterValueException("Unable to update disk service by another domain admin with id " + userId); + throw new InvalidParameterValueException("Unable to update service by another domain admin with id " + userId); } } for (Long domainId : filteredDomainIds) { if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) { - throw new InvalidParameterValueException("Unable to update disk service by another domain admin with id " + userId); + throw new InvalidParameterValueException("Unable to update service by another domain admin with id " + userId); } } } else if (account.getType() != Account.ACCOUNT_TYPE_ADMIN) { @@ -5135,11 +5135,11 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati List filteredDomainIds = filterChildSubDomains(domainIds); List detailsVO = new ArrayList<>(); for (Long domainId : filteredDomainIds) { - detailsVO.add(new NetworkOfferingDetailsVO(offering.getId(), Detail.domainid, String.valueOf(domainId))); + detailsVO.add(new NetworkOfferingDetailsVO(offering.getId(), Detail.domainid, String.valueOf(domainId), false)); } if (CollectionUtils.isNotEmpty(zoneIds)) { for (Long zoneId : zoneIds) { - detailsVO.add(new NetworkOfferingDetailsVO(offering.getId(), Detail.zoneid, String.valueOf(zoneId))); + detailsVO.add(new NetworkOfferingDetailsVO(offering.getId(), Detail.zoneid, String.valueOf(zoneId), false)); } } if (!detailsVO.isEmpty()) { @@ -5507,10 +5507,13 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Availability availability = null; final String state = cmd.getState(); final String tags = cmd.getTags(); + final List domainIds = cmd.getDomainIds(); + final List zoneIds = cmd.getZoneIds(); CallContext.current().setEventDetails(" Id: " + id); // Verify input parameters final NetworkOfferingVO offeringToUpdate = _networkOfferingDao.findById(id); + List existingDomainIds = networkOfferingDetailsDao.findDomainIds(id); if (offeringToUpdate == null) { throw new InvalidParameterValueException("unable to find network offering " + id); } @@ -5520,91 +5523,148 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException("Can't update system network offerings"); } + // check if valid domain + if (CollectionUtils.isNotEmpty(domainIds)) { + for (final Long domainId: domainIds) { + if (_domainDao.findById(domainId) == null) { + throw new InvalidParameterValueException("Please specify a valid domain id"); + } + } + } + + // check if valid zone + if (CollectionUtils.isNotEmpty(zoneIds)) { + for (Long zoneId : zoneIds) { + if (_zoneDao.findById(zoneId) == null) + throw new InvalidParameterValueException("Please specify a valid zone id"); + } + } + + // Filter child domains when both parent and child domains are present + List filteredDomainIds = filterChildSubDomains(domainIds); + if (CollectionUtils.isNotEmpty(existingDomainIds) && CollectionUtils.isNotEmpty(filteredDomainIds)) { + filteredDomainIds.removeIf(existingDomainIds::contains); + for (Long domainId : filteredDomainIds) { + for (Long existingDomainId : existingDomainIds) { + if (_domainDao.isChildDomain(existingDomainId, domainId)) { + throw new InvalidParameterValueException("Unable to update network offering for domain " + _domainDao.findById(domainId).getUuid() + " as offering is already available for parent domain"); + } + } + } + } + + List filteredZoneIds = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(zoneIds)) { + filteredZoneIds.addAll(zoneIds); + List existingZoneIds = networkOfferingDetailsDao.findZoneIds(id); + if (CollectionUtils.isNotEmpty(existingZoneIds)) { + filteredZoneIds.removeIf(existingZoneIds::contains); + } + } + final NetworkOfferingVO offering = _networkOfferingDao.createForUpdate(id); - if (name != null) { - offering.setName(name); - } + boolean updateNeeded = name != null || displayText != null || sortKey != null || + state != null || tags != null || availabilityStr != null || maxconn != null; - if (displayText != null) { - offering.setDisplayText(displayText); - } - - if (sortKey != null) { - offering.setSortKey(sortKey); - } - - if (state != null) { - boolean validState = false; - for (final NetworkOffering.State st : NetworkOffering.State.values()) { - if (st.name().equalsIgnoreCase(state)) { - validState = true; - offering.setState(st); - } + if(updateNeeded) { + if (name != null) { + offering.setName(name); } - if (!validState) { - throw new InvalidParameterValueException("Incorrect state value: " + state); + + if (displayText != null) { + offering.setDisplayText(displayText); } - } - if (tags != null) { - List dataCenters = _zoneDao.listAll(); - TrafficType trafficType = offeringToUpdate.getTrafficType(); - String oldTags = offeringToUpdate.getTags(); + if (sortKey != null) { + offering.setSortKey(sortKey); + } - for (DataCenterVO dataCenter : dataCenters) { - long zoneId = dataCenter.getId(); - long newPhysicalNetworkId = _networkModel.findPhysicalNetworkId(zoneId, tags, trafficType); - if (oldTags != null) { - long oldPhysicalNetworkId = _networkModel.findPhysicalNetworkId(zoneId, oldTags, trafficType); - if (newPhysicalNetworkId != oldPhysicalNetworkId) { - throw new InvalidParameterValueException("New tags: selects different physical network for zone " + zoneId); + if (state != null) { + boolean validState = false; + for (final NetworkOffering.State st : NetworkOffering.State.values()) { + if (st.name().equalsIgnoreCase(state)) { + validState = true; + offering.setState(st); } } - } - - offering.setTags(tags); - } - - // Verify availability - if (availabilityStr != null) { - for (final Availability avlb : Availability.values()) { - if (avlb.name().equalsIgnoreCase(availabilityStr)) { - availability = avlb; + if (!validState) { + throw new InvalidParameterValueException("Incorrect state value: " + state); } } - if (availability == null) { - throw new InvalidParameterValueException("Invalid value for Availability. Supported types: " + Availability.Required + ", " + Availability.Optional); - } else { - if (availability == NetworkOffering.Availability.Required) { - final boolean canOffBeRequired = offeringToUpdate.getGuestType() == GuestType.Isolated && _networkModel.areServicesSupportedByNetworkOffering( - offeringToUpdate.getId(), Service.SourceNat); - if (!canOffBeRequired) { - throw new InvalidParameterValueException("Availability can be " + NetworkOffering.Availability.Required + " only for networkOfferings of type " - + GuestType.Isolated + " and with " + Service.SourceNat.getName() + " enabled"); - } - // only one network offering in the system can be Required - final List offerings = _networkOfferingDao.listByAvailability(Availability.Required, false); - if (!offerings.isEmpty() && offerings.get(0).getId() != offeringToUpdate.getId()) { - throw new InvalidParameterValueException("System already has network offering id=" + offerings.get(0).getId() + " with availability " - + Availability.Required); + if (tags != null) { + List dataCenters = _zoneDao.listAll(); + TrafficType trafficType = offeringToUpdate.getTrafficType(); + String oldTags = offeringToUpdate.getTags(); + + for (DataCenterVO dataCenter : dataCenters) { + long zoneId = dataCenter.getId(); + long newPhysicalNetworkId = _networkModel.findPhysicalNetworkId(zoneId, tags, trafficType); + if (oldTags != null) { + long oldPhysicalNetworkId = _networkModel.findPhysicalNetworkId(zoneId, oldTags, trafficType); + if (newPhysicalNetworkId != oldPhysicalNetworkId) { + throw new InvalidParameterValueException("New tags: selects different physical network for zone " + zoneId); + } } } - offering.setAvailability(availability); + + offering.setTags(tags); } - } - if (_ntwkOffServiceMapDao.areServicesSupportedByNetworkOffering(offering.getId(), Service.Lb)) { - if (maxconn != null) { - offering.setConcurrentConnections(maxconn); + + // Verify availability + if (availabilityStr != null) { + for (final Availability avlb : Availability.values()) { + if (avlb.name().equalsIgnoreCase(availabilityStr)) { + availability = avlb; + } + } + if (availability == null) { + throw new InvalidParameterValueException("Invalid value for Availability. Supported types: " + Availability.Required + ", " + Availability.Optional); + } else { + if (availability == NetworkOffering.Availability.Required) { + final boolean canOffBeRequired = offeringToUpdate.getGuestType() == GuestType.Isolated && _networkModel.areServicesSupportedByNetworkOffering( + offeringToUpdate.getId(), Service.SourceNat); + if (!canOffBeRequired) { + throw new InvalidParameterValueException("Availability can be " + NetworkOffering.Availability.Required + " only for networkOfferings of type " + + GuestType.Isolated + " and with " + Service.SourceNat.getName() + " enabled"); + } + + // only one network offering in the system can be Required + final List offerings = _networkOfferingDao.listByAvailability(Availability.Required, false); + if (!offerings.isEmpty() && offerings.get(0).getId() != offeringToUpdate.getId()) { + throw new InvalidParameterValueException("System already has network offering id=" + offerings.get(0).getId() + " with availability " + + Availability.Required); + } + } + offering.setAvailability(availability); + } + } + if (_ntwkOffServiceMapDao.areServicesSupportedByNetworkOffering(offering.getId(), Service.Lb)) { + if (maxconn != null) { + offering.setConcurrentConnections(maxconn); + } + } + + if (!_networkOfferingDao.update(id, offering)) { + return null; } } - if (_networkOfferingDao.update(id, offering)) { - return _networkOfferingDao.findById(id); - } else { - return null; + List detailsVO = new ArrayList<>(); + for (Long domainId : filteredDomainIds) { + detailsVO.add(new NetworkOfferingDetailsVO(id, Detail.domainid, String.valueOf(domainId), false)); } + for (Long zoneId : filteredZoneIds) { + detailsVO.add(new NetworkOfferingDetailsVO(id, Detail.zoneid, String.valueOf(zoneId), false)); + } + if (!detailsVO.isEmpty()) { + for (NetworkOfferingDetailsVO detailVO : detailsVO) { + networkOfferingDetailsDao.persist(detailVO); + } + } + + return _networkOfferingDao.findById(id); } @Override diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 8aeb3c682a9..12af145c876 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -118,6 +118,7 @@ import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.network.vpn.Site2SiteVpnManager; import com.cloud.offering.DiskOffering; +import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.projects.Project; import com.cloud.projects.Project.ListProjectResourcesCriteria; @@ -2859,6 +2860,21 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + dof); } + @Override + public void checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException { + for (SecurityChecker checker : _securityCheckers) { + if (checker.checkAccess(account, nof, zone)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Access granted to " + account + " to " + nof + " by " + checker.getName()); + } + return; + } + } + + assert false : "How can all of the security checkers pass on checking this caller?"; + throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + nof); + } + @Override public void checkAccess(User user, ControlledEntity entity) throws PermissionDeniedException { for (SecurityChecker checker : _securityCheckers) { diff --git a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java index c35fb215e10..4e914157702 100644 --- a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java +++ b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java @@ -42,6 +42,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.offering.DiskOffering; +import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.utils.Pair; @@ -222,6 +223,11 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco // TODO Auto-generated method stub } + @Override + public void checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException { + // TODO Auto-generated method stub + } + @Override public Long checkAccessAndSpecifyAuthority(Account caller, Long zoneId) { // TODO Auto-generated method stub