diff --git a/api/src/com/cloud/user/DomainService.java b/api/src/com/cloud/user/DomainService.java index 4c1f93d0737..3ccfcbcea4c 100644 --- a/api/src/com/cloud/user/DomainService.java +++ b/api/src/com/cloud/user/DomainService.java @@ -56,4 +56,14 @@ public interface DomainService { */ Domain findDomainByPath(String domainPath); + /** + * finds the domain by either id or provided path + * + * @param id the domain id + * @param domainPath the domain path use to lookup a domain + * + * @return domainId the long value of the domain ID, or null if no domain id exists with provided id/path + */ + Domain findDomainByIdOrPath(Long id, String domainPath); + } diff --git a/debian/control b/debian/control index 6452c03c5d7..8d40059dccb 100644 --- a/debian/control +++ b/debian/control @@ -15,14 +15,14 @@ Description: A common package which contains files which are shared by several C Package: cloudstack-management Architecture: all -Depends: ${misc:Depends}, ${python:Depends}, cloudstack-common (= ${source:Version}), tomcat6 | tomcat7, sudo, jsvc, python-mysql.connector, libmysql-java, augeas-tools, mysql-client, adduser, bzip2, ipmitool +Depends: ${misc:Depends}, ${python:Depends}, cloudstack-common (= ${source:Version}), tomcat6 | tomcat7, sudo, jsvc, python-mysql.connector, libmysql-java, augeas-tools, mysql-client, adduser, bzip2, ipmitool, lsb-release Conflicts: cloud-server, cloud-client, cloud-client-ui Description: CloudStack server library The CloudStack management server Package: cloudstack-agent Architecture: all -Depends: ${misc:Depends}, ${python:Depends}, openjdk-8-jre-headless | openjdk-7-jre-headless, cloudstack-common (= ${source:Version}), lsb-base (>= 4.0), libcommons-daemon-java, openssh-client, qemu-kvm (>= 1.0), libvirt-bin (>= 0.9.8), uuid-runtime, iproute, ebtables, vlan, jsvc, ipset, python-libvirt, ethtool, iptables +Depends: ${misc:Depends}, ${python:Depends}, openjdk-8-jre-headless | openjdk-7-jre-headless, cloudstack-common (= ${source:Version}), lsb-base (>= 4.0), libcommons-daemon-java, openssh-client, qemu-kvm (>= 1.0), libvirt-bin (>= 0.9.8), uuid-runtime, iproute, ebtables, vlan, jsvc, ipset, python-libvirt, ethtool, iptables, lsb-release Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts Description: CloudStack agent The CloudStack agent is in charge of managing shared computing resources in diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index 666aedd7235..9c296cb4b51 100644 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -1003,17 +1003,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer final Map requestParameters) throws CloudAuthenticationException { // We will always use domainId first. If that does not exist, we will use domain name. If THAT doesn't exist // we will default to ROOT - if (domainId == null) { - if (domainPath == null || domainPath.trim().length() == 0) { - domainId = Domain.ROOT_DOMAIN; - } else { - final Domain domainObj = domainMgr.findDomainByPath(domainPath); - if (domainObj != null) { - domainId = domainObj.getId(); - } else { // if an unknown path is passed in, fail the login call - throw new CloudAuthenticationException("Unable to find the domain from the path " + domainPath); - } - } + final Domain userDomain = _domainMgr.findDomainByIdOrPath(domainId, domainPath); + if (userDomain == null || userDomain.getId() < 1L) { + throw new CloudAuthenticationException("Unable to find the domain from the path " + domainPath); + } else { + domainId = userDomain.getId(); } final UserAccount userAcct = accountMgr.authenticateUser(username, password, domainId, loginIpAddress, requestParameters); diff --git a/server/src/com/cloud/api/auth/DefaultLoginAPIAuthenticatorCmd.java b/server/src/com/cloud/api/auth/DefaultLoginAPIAuthenticatorCmd.java index d6eac7864d4..c83e7080edc 100644 --- a/server/src/com/cloud/api/auth/DefaultLoginAPIAuthenticatorCmd.java +++ b/server/src/com/cloud/api/auth/DefaultLoginAPIAuthenticatorCmd.java @@ -16,6 +16,9 @@ // under the License. package com.cloud.api.auth; +import com.cloud.domain.Domain; +import com.cloud.user.User; +import com.cloud.user.UserAccount; import org.apache.cloudstack.api.ApiServerService; import com.cloud.api.response.ApiResponseSerializer; import com.cloud.exception.CloudAuthenticationException; @@ -156,6 +159,16 @@ public class DefaultLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthe if (username != null) { final String pwd = ((password == null) ? null : password[0]); try { + final Domain userDomain = _domainService.findDomainByIdOrPath(domainId, domain); + if (userDomain != null) { + domainId = userDomain.getId(); + } else { + throw new CloudAuthenticationException("Unable to find the domain from the path " + domain); + } + final UserAccount userAccount = _accountService.getActiveUserAccount(username[0], domainId); + if (userAccount == null || !(User.Source.UNKNOWN.equals(userAccount.getSource()) || User.Source.LDAP.equals(userAccount.getSource()))) { + throw new CloudAuthenticationException("User is not allowed CloudStack login"); + } return ApiResponseSerializer.toSerializedString(_apiServer.loginUser(session, username[0], pwd, domainId, domain, remoteAddress, params), responseType); } catch (final CloudAuthenticationException ex) { diff --git a/server/src/com/cloud/api/query/QueryManagerImpl.java b/server/src/com/cloud/api/query/QueryManagerImpl.java index 3cdd4bb791f..95f260d53c2 100644 --- a/server/src/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/com/cloud/api/query/QueryManagerImpl.java @@ -3070,9 +3070,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q boolean listAll = false; if (templateFilter != null && templateFilter == TemplateFilter.all) { - if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL) { + if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { throw new InvalidParameterValueException("Filter " + TemplateFilter.all - + " can be specified by admin only"); + + " can be specified by root admin only"); } listAll = true; } diff --git a/server/src/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/com/cloud/template/HypervisorTemplateAdapter.java index 13649745ec2..bfb146bd5ee 100644 --- a/server/src/com/cloud/template/HypervisorTemplateAdapter.java +++ b/server/src/com/cloud/template/HypervisorTemplateAdapter.java @@ -394,7 +394,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { // publish zone-wide usage event Long sZoneId = ((ImageStoreEntity)imageStore).getDataCenterId(); if (sZoneId != null) { - UsageEventUtils.publishUsageEvent(eventType, template.getAccountId(), sZoneId, template.getId(), null, null, null); + UsageEventUtils.publishUsageEvent(eventType, template.getAccountId(), sZoneId, template.getId(), null, VirtualMachineTemplate.class.getName(), template.getUuid()); } s_logger.info("Delete template from image store: " + imageStore.getName()); diff --git a/server/src/com/cloud/user/DomainManagerImpl.java b/server/src/com/cloud/user/DomainManagerImpl.java index e056e2d87ed..13dcd90f421 100644 --- a/server/src/com/cloud/user/DomainManagerImpl.java +++ b/server/src/com/cloud/user/DomainManagerImpl.java @@ -73,6 +73,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.ReservationContext; import com.cloud.vm.ReservationContextImpl; +import com.google.common.base.Strings; @Component public class DomainManagerImpl extends ManagerBase implements DomainManager, DomainService { @@ -218,6 +219,25 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom return _domainDao.findDomainByPath(domainPath); } + @Override + public Domain findDomainByIdOrPath(final Long id, final String domainPath) { + Long domainId = id; + if (domainId == null || domainId < 1L) { + if (Strings.isNullOrEmpty(domainPath) || domainPath.trim().isEmpty()) { + domainId = Domain.ROOT_DOMAIN; + } else { + final Domain domainVO = findDomainByPath(domainPath.trim()); + if (domainVO != null) { + return domainVO; + } + } + } + if (domainId != null && domainId > 0L) { + return _domainDao.findById(domainId); + } + return null; + } + @Override public Set getDomainParentIds(long domainId) { return _domainDao.getDomainParentIds(domainId); diff --git a/server/test/com/cloud/template/HypervisorTemplateAdapterTest.java b/server/test/com/cloud/template/HypervisorTemplateAdapterTest.java new file mode 100644 index 00000000000..50ed8707326 --- /dev/null +++ b/server/test/com/cloud/template/HypervisorTemplateAdapterTest.java @@ -0,0 +1,285 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.template; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.TemplateApiResult; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.events.Event; +import org.apache.cloudstack.framework.events.EventBus; +import org.apache.cloudstack.framework.events.EventBusException; +import org.apache.cloudstack.framework.messagebus.MessageBus; +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.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.event.UsageEventVO; +import com.cloud.event.dao.UsageEventDao; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.TemplateProfile; +import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; +import com.cloud.storage.dao.VMTemplateZoneDao; +import com.cloud.storage.VMTemplateVO; +import com.cloud.user.AccountVO; +import com.cloud.user.ResourceLimitService; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.component.ComponentContext; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(ComponentContext.class) +public class HypervisorTemplateAdapterTest { + @Mock + EventBus _bus; + List events = new ArrayList<>(); + + @Mock + TemplateManager _templateMgr; + + @Mock + TemplateService _templateService; + + @Mock + TemplateDataFactory _dataFactory; + + @Mock + VMTemplateZoneDao _templateZoneDao; + + @Mock + TemplateDataStoreDao _templateStoreDao; + + @Mock + UsageEventDao _usageEventDao; + + @Mock + ResourceLimitService _resourceManager; + + @Mock + MessageBus _messageBus; + + @Mock + AccountDao _accountDao; + + @Mock + DataCenterDao _dcDao; + + @Mock + ConfigurationDao _configDao; + + @InjectMocks + HypervisorTemplateAdapter _adapter; + + //UsageEventUtils reflection abuse helpers + private Map oldFields = new HashMap<>(); + private List usageEvents = new ArrayList<>(); + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + public UsageEventUtils setupUsageUtils() throws EventBusException { + Mockito.when(_configDao.getValue(eq("publish.usage.events"))).thenReturn("true"); + Mockito.when(_usageEventDao.persist(Mockito.any(UsageEventVO.class))).then(new Answer() { + @Override public Void answer(InvocationOnMock invocation) throws Throwable { + UsageEventVO vo = (UsageEventVO)invocation.getArguments()[0]; + usageEvents.add(vo); + return null; + } + }); + + Mockito.when(_usageEventDao.listAll()).thenReturn(usageEvents); + + doAnswer(new Answer() { + @Override public Void answer(InvocationOnMock invocation) throws Throwable { + Event event = (Event)invocation.getArguments()[0]; + events.add(event); + return null; + } + }).when(_bus).publish(any(Event.class)); + + PowerMockito.mockStatic(ComponentContext.class); + when(ComponentContext.getComponent(eq(EventBus.class))).thenReturn(_bus); + + UsageEventUtils utils = new UsageEventUtils(); + + Map usageUtilsFields = new HashMap(); + usageUtilsFields.put("usageEventDao", "_usageEventDao"); + usageUtilsFields.put("accountDao", "_accountDao"); + usageUtilsFields.put("dcDao", "_dcDao"); + usageUtilsFields.put("configDao", "_configDao"); + + for (String fieldName : usageUtilsFields.keySet()) { + try { + Field f = UsageEventUtils.class.getDeclaredField(fieldName); + f.setAccessible(true); + //Remember the old fields for cleanup later (see cleanupUsageUtils) + Field staticField = UsageEventUtils.class.getDeclaredField("s_" + fieldName); + staticField.setAccessible(true); + oldFields.put(f.getName(), staticField.get(null)); + f.set(utils, + this.getClass() + .getDeclaredField( + usageUtilsFields.get(fieldName)) + .get(this)); + } catch (IllegalArgumentException | IllegalAccessException + | NoSuchFieldException | SecurityException e) { + e.printStackTrace(); + } + + } + try { + Method method = UsageEventUtils.class.getDeclaredMethod("init"); + method.setAccessible(true); + method.invoke(utils); + } catch (SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException e) { + e.printStackTrace(); + } + + return utils; + } + + public void cleanupUsageUtils() { + UsageEventUtils utils = new UsageEventUtils(); + + for (String fieldName : oldFields.keySet()) { + try { + Field f = UsageEventUtils.class.getDeclaredField(fieldName); + f.setAccessible(true); + f.set(utils, oldFields.get(fieldName)); + } catch (IllegalArgumentException | IllegalAccessException + | NoSuchFieldException | SecurityException e) { + e.printStackTrace(); + } + + } + try { + Method method = UsageEventUtils.class.getDeclaredMethod("init"); + method.setAccessible(true); + method.invoke(utils); + } catch (SecurityException | NoSuchMethodException + | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + e.printStackTrace(); + } + } + + @Test + public void testEmitDeleteEventUuid() throws InterruptedException, ExecutionException, EventBusException { + //All the mocks required for this test to work. + ImageStoreEntity store = mock(ImageStoreEntity.class); + when(store.getId()).thenReturn(1l); + when(store.getDataCenterId()).thenReturn(1l); + when(store.getName()).thenReturn("Test Store"); + + TemplateDataStoreVO dataStoreVO = mock(TemplateDataStoreVO.class); + when(dataStoreVO.getDownloadState()).thenReturn(Status.DOWNLOADED); + + TemplateInfo info = mock(TemplateInfo.class); + when(info.getDataStore()).thenReturn(store); + + VMTemplateVO template = mock(VMTemplateVO.class); + when(template.getId()).thenReturn(1l); + when(template.getName()).thenReturn("Test Template"); + when(template.getFormat()).thenReturn(ImageFormat.QCOW2); + when(template.getAccountId()).thenReturn(1l); + when(template.getUuid()).thenReturn("Test UUID"); //TODO possibly return this from method for comparison, if things work how i want + + TemplateProfile profile = mock(TemplateProfile.class); + when(profile.getTemplate()).thenReturn(template); + when(profile.getZoneId()).thenReturn(1l); + + TemplateApiResult result = mock(TemplateApiResult.class); + when(result.isSuccess()).thenReturn(true); + when(result.isFailed()).thenReturn(false); + + @SuppressWarnings("unchecked") + AsyncCallFuture future = mock(AsyncCallFuture.class); + when(future.get()).thenReturn(result); + + AccountVO acct = mock(AccountVO.class); + when(acct.getId()).thenReturn(1l); + when(acct.getDomainId()).thenReturn(1l); + + when(_templateMgr.getImageStoreByTemplate(anyLong(), anyLong())).thenReturn(Collections.singletonList((DataStore)store)); + when(_templateStoreDao.listByTemplateStore(anyLong(), anyLong())).thenReturn(Collections.singletonList(dataStoreVO)); + when(_dataFactory.getTemplate(anyLong(), any(DataStore.class))).thenReturn(info); + when(_dataFactory.listTemplateOnCache(anyLong())).thenReturn(Collections.singletonList(info)); + when(_templateService.deleteTemplateAsync(any(TemplateInfo.class))).thenReturn(future); + when(_accountDao.findById(anyLong())).thenReturn(acct); + when(_accountDao.findByIdIncludingRemoved(anyLong())).thenReturn(acct); + + //Test actually begins here. + setupUsageUtils(); + + _adapter.delete(profile); + Assert.assertNotNull(usageEvents); + Assert.assertNotNull(events); + Assert.assertEquals(1, events.size()); + + Event event = events.get(0); + Assert.assertNotNull(event); + Assert.assertNotNull(event.getResourceType()); + Assert.assertEquals(VirtualMachineTemplate.class.getName(), event.getResourceType()); + Assert.assertNotNull(event.getResourceUUID()); + Assert.assertEquals("Test UUID", event.getResourceUUID()); + Assert.assertEquals(EventTypes.EVENT_TEMPLATE_DELETE, event.getEventType()); + + + cleanupUsageUtils(); + } +} diff --git a/server/test/com/cloud/user/DomainManagerImplTest.java b/server/test/com/cloud/user/DomainManagerImplTest.java new file mode 100644 index 00000000000..82d54912b5e --- /dev/null +++ b/server/test/com/cloud/user/DomainManagerImplTest.java @@ -0,0 +1,137 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.user; + +import com.cloud.configuration.dao.ResourceCountDao; +import com.cloud.configuration.dao.ResourceLimitDao; +import com.cloud.dc.dao.DedicatedResourceDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.network.dao.NetworkDomainDao; +import com.cloud.projects.ProjectManager; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.user.dao.AccountDao; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.region.RegionManager; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.inject.Inject; +import java.lang.reflect.Field; + +@RunWith(MockitoJUnitRunner.class) +public class DomainManagerImplTest { + @Mock + DomainDao _domainDao; + @Mock + AccountManager _accountMgr; + @Mock + ResourceCountDao _resourceCountDao; + @Mock + AccountDao _accountDao; + @Mock + DiskOfferingDao _diskOfferingDao; + @Mock + ServiceOfferingDao _offeringsDao; + @Mock + ProjectDao _projectDao; + @Mock + ProjectManager _projectMgr; + @Mock + RegionManager _regionMgr; + @Mock + ResourceLimitDao _resourceLimitDao; + @Mock + DedicatedResourceDao _dedicatedDao; + @Mock + NetworkOrchestrationService _networkMgr; + @Mock + NetworkDomainDao _networkDomainDao; + @Mock + MessageBus _messageBus; + + DomainManagerImpl domainManager; + + @Before + public void setup() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + domainManager = new DomainManagerImpl(); + for (Field field : DomainManagerImpl.class.getDeclaredFields()) { + if (field.getAnnotation(Inject.class) != null) { + field.setAccessible(true); + try { + Field mockField = this.getClass().getDeclaredField( + field.getName()); + field.set(domainManager, mockField.get(this)); + } catch (Exception ignored) { + } + } + } + } + + @Test + public void testFindDomainByIdOrPathNullOrEmpty() { + final DomainVO domain = new DomainVO("someDomain", 123, 1L, "network.domain"); + Mockito.when(_domainDao.findById(1L)).thenReturn(domain); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(null, null)); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(0L, "")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(-1L, " ")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(null, " ")); + } + + @Test + public void testFindDomainByIdOrPathValidPathAndInvalidId() { + final DomainVO domain = new DomainVO("someDomain", 123, 1L, "network.domain"); + Mockito.when(_domainDao.findDomainByPath(Mockito.eq("/someDomain/"))).thenReturn(domain); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(null, "/someDomain/")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(0L, " /someDomain/")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(-1L, "/someDomain/ ")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(null, " /someDomain/ ")); + } + + @Test + public void testFindDomainByIdOrPathInvalidPathAndInvalidId() { + Mockito.when(_domainDao.findDomainByPath(Mockito.anyString())).thenReturn(null); + Assert.assertNull(domainManager.findDomainByIdOrPath(null, "/nonExistingDomain/")); + Assert.assertNull(domainManager.findDomainByIdOrPath(0L, " /nonExistingDomain/")); + Assert.assertNull(domainManager.findDomainByIdOrPath(-1L, "/nonExistingDomain/ ")); + Assert.assertNull(domainManager.findDomainByIdOrPath(null, " /nonExistingDomain/ ")); + } + + + @Test + public void testFindDomainByIdOrPathValidId() { + final DomainVO domain = new DomainVO("someDomain", 123, 1L, "network.domain"); + Mockito.when(_domainDao.findById(1L)).thenReturn(domain); + Mockito.when(_domainDao.findDomainByPath(Mockito.eq("/validDomain/"))).thenReturn(new DomainVO()); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(1L, null)); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(1L, "")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(1L, " ")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(1L, " ")); + Assert.assertEquals(domain, domainManager.findDomainByIdOrPath(1L, "/validDomain/")); + } + +} diff --git a/server/test/com/cloud/user/MockDomainManagerImpl.java b/server/test/com/cloud/user/MockDomainManagerImpl.java index cdae887cee6..394c3e22719 100644 --- a/server/test/com/cloud/user/MockDomainManagerImpl.java +++ b/server/test/com/cloud/user/MockDomainManagerImpl.java @@ -91,6 +91,11 @@ public class MockDomainManagerImpl extends ManagerBase implements DomainManager, return null; } + @Override + public DomainVO findDomainByIdOrPath(Long id, String domainPath) { + return null; + } + @Override public Set getDomainParentIds(long domainId) { // TODO Auto-generated method stub diff --git a/test/integration/component/test_templates.py b/test/integration/component/test_templates.py index b1e7e7c6546..c8384d97c89 100644 --- a/test/integration/component/test_templates.py +++ b/test/integration/component/test_templates.py @@ -22,6 +22,7 @@ from marvin.cloudstackTestCase import cloudstackTestCase, unittest from marvin.cloudstackAPI import listZones from marvin.lib.utils import (cleanup_resources) from marvin.lib.base import (Account, + Domain, Template, ServiceOffering, VirtualMachine, @@ -51,6 +52,7 @@ class Services: # username "password": "password", }, + "testdomain": {"name": "test"}, "service_offering": { "name": "Tiny Instance", "displaytext": "Tiny Instance", @@ -602,3 +604,77 @@ class TestTemplates(cloudstackTestCase): "Check the state of VM created from Template" ) return + + +class TestListTemplate(cloudstackTestCase): + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.hypervisor = self.testClient.getHypervisorInfo() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + self.services = Services().services + # Get Zone, Domain and templates + self.domain = get_domain(self.apiclient) + self.account = Account.create( + self.apiclient, + self.services["account"], + domainid=self.domain.id + ) + self.newdomain = Domain.create( + self.apiclient, + self.services["testdomain"], + parentdomainid=self.domain.id + ) + self.newdomain_account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.newdomain.id + ) + self.cleanup = [ + self.account, + self.newdomain_account, + self.newdomain, + ] + + + def tearDown(self): + try: + # Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_01_list_templates_with_templatefilter_all_normal_user(self): + """ + Test list templates with templatefilter=all is not permitted for normal user + """ + + user_api_client = self.testClient.getUserApiClient( + UserName=self.account.name, + DomainName=self.account.domain) + try: + list_template_response = Template.list(self.user_api_client, templatefilter='all') + self.fail("Regular User is able to use templatefilter='all' in listTemplates API call") + except Exception as e: + self.debug("ListTemplates API with templatefilter='all' is not permitted for normal user") + + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_02_list_templates_with_templatefilter_all_domain_admin(self): + """ + Test list templates with templatefilter=all is not permitted for domain admin + """ + + domain_user_api_client = self.testClient.getUserApiClient( + UserName=self.newdomain_account.name, + DomainName=self.newdomain_account.domain) + try: + list_template_response = Template.list(self.domain_user_api_client, templatefilter='all') + self.fail("Domain admin is able to use templatefilter='all' in listTemplates API call") + except Exception as e: + self.debug("ListTemplates API with templatefilter='all' is not permitted for domain admin user") diff --git a/test/integration/smoke/test_login.py b/test/integration/smoke/test_login.py new file mode 100644 index 00000000000..5855feabd9f --- /dev/null +++ b/test/integration/smoke/test_login.py @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.lib.utils import * +from marvin.lib.base import * +from marvin.lib.common import * + +import requests + + +class TestLogin(cloudstackTestCase): + """ + Tests default login API handler + """ + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.server_details = self.config.__dict__["mgtSvr"][0].__dict__ + self.server_url = "http://%s:8080/client/api" % self.server_details['mgtSvrIp'] + self.testdata = { + "account": { + "email": "login-user@test.cloud", + "firstname": "TestLoginFirstName", + "lastname": "TestLoginLastName", + "username": "testloginuser-", + "password": "password123", + } + } + self.cleanup = [] + + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + + def login(self, username, password, domain="/"): + """ + Logs in and returns a session to be used for subsequent API calls + """ + args = {} + args["command"] = 'login' + args["username"] = username + args["password"] = password + args["domain"] = domain + args["response"] = "json" + + session = requests.Session() + + try: + resp = session.post(self.server_url, params=args, verify=False) + except requests.exceptions.ConnectionError, e: + self.fail("Failed to attempt login request to mgmt server") + return None, None + + return resp, session + + + @attr(tags = ["devcloud", "advanced", "advancedns", "advancedsg", "smoke", + "basic", "sg"], required_hardware="false") + def login_test_saml_user(self): + """ + Tests that SAML users are not allowed CloudStack local log in + + Creates account across various account types and converts them to + a SAML user and tests that they are not able to log in; then + converts them back as a CloudStack user account and verifies that + they are allowed to log in and make API requests + """ + # Tests across various account types: 0=User, 1=Root Admin, 2=Domain Admin + for account_type in range(0, 3): + account = Account.create( + self.apiclient, + self.testdata['account'], + admin=account_type + ) + self.cleanup.append(account) + + username = account.user[0].username + password = self.testdata['account']['password'] + + # Convert newly created account user to SAML user + user_id = self.dbclient.execute("select id from user where uuid='%s'" % account.user[0].id)[0][0] + self.dbclient.execute("update user set source='SAML2' where id=%d" % user_id) + + response, session = self.login(username, password) + self.assertEqual( + response.json()['loginresponse']['errorcode'], + 531, + "SAML user should not be allowed to log in, error code 531 not returned" + ) + self.assertEqual( + response.json()['loginresponse']['errortext'], + "User is not allowed CloudStack login", + "Invalid error message returned, SAML user should not be allowed to log in" + ) + + # Convert newly created account user back to normal source + self.dbclient.execute("update user set source='UNKNOWN' where id=%d" % user_id) + + response, session = self.login(username, password) + self.assertEqual( + response.status_code, + 200, + "Login response code was not 200" + ) + self.assertTrue( + len(response.json()['loginresponse']['sessionkey']) > 0, + "Invalid session key received" + ) + + args = {} + args["command"] = 'listUsers' + args["listall"] = 'true' + args["response"] = "json" + response = session.get(self.server_url, params=args) + self.assertEqual( + response.status_code, + 200, + "listUsers response code was not 200" + ) + self.assertTrue( + len(response.json()['listusersresponse']['user']) > 0, + "listUsers list is empty or zero" + )