| // 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.api; | |
| import com.cloud.domain.Domain; | |
| import com.cloud.domain.DomainVO; | |
| import com.cloud.exception.CloudAuthenticationException; | |
| import com.cloud.user.Account; | |
| import com.cloud.user.AccountManager; | |
| import com.cloud.user.DomainManager; | |
| import com.cloud.exception.OriginDeniedException; | |
| import com.cloud.exception.PermissionDeniedException; | |
| import com.cloud.user.User; | |
| import com.cloud.user.UserAccount; | |
| import com.cloud.user.UserVO; | |
| import com.cloud.utils.exception.CloudRuntimeException; | |
| import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; | |
| import org.apache.cloudstack.api.ApiConstants; | |
| import org.apache.cloudstack.api.ResponseObject; | |
| import org.apache.cloudstack.api.response.LoginCmdResponse; | |
| import org.apache.cloudstack.config.ApiServiceConfiguration; | |
| import org.apache.cloudstack.framework.config.ConfigKey; | |
| import org.apache.cloudstack.resourcedetail.UserDetailVO; | |
| import org.apache.cloudstack.user.UserPasswordResetManager; | |
| import org.junit.After; | |
| import org.junit.AfterClass; | |
| import org.junit.Assert; | |
| import org.junit.Before; | |
| import org.junit.BeforeClass; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.mockito.InjectMocks; | |
| import org.mockito.Mock; | |
| import org.mockito.MockedConstruction; | |
| import org.mockito.Mockito; | |
| import org.mockito.junit.MockitoJUnitRunner; | |
| import org.springframework.test.util.ReflectionTestUtils; | |
| import java.lang.reflect.Field; | |
| import java.net.InetAddress; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import static org.apache.cloudstack.api.ApiConstants.PASSWORD_CHANGE_REQUIRED; | |
| import static org.apache.cloudstack.user.UserPasswordResetManager.UserPasswordResetEnabled; | |
| import static org.mockito.ArgumentMatchers.any; | |
| import static org.mockito.ArgumentMatchers.anyLong; | |
| import static org.mockito.ArgumentMatchers.anyString; | |
| import static org.mockito.ArgumentMatchers.eq; | |
| import static org.mockito.Mockito.mock; | |
| import javax.servlet.http.HttpSession; | |
| @RunWith(MockitoJUnitRunner.class) | |
| public class ApiServerTest { | |
| @InjectMocks | |
| ApiServer apiServer = new ApiServer(); | |
| @Mock | |
| UserPasswordResetManager userPasswordResetManager; | |
| @Mock | |
| DomainManager domainManager; | |
| @Mock | |
| AccountManager accountManager; | |
| @Mock | |
| HttpSession session; | |
| private static final String DEFAULT_CIDR_CHECKS_ENABLED = ApiServiceConfiguration.ApiSourceCidrChecksEnabled.defaultValue(); | |
| private static final String DEFAULT_ALLOWED_CIDRS = ApiServiceConfiguration.ApiAllowedSourceCidrList.defaultValue(); | |
| @BeforeClass | |
| public static void beforeClass() throws Exception { | |
| overrideDefaultConfigValue(UserPasswordResetEnabled, "_value", true); | |
| } | |
| @AfterClass | |
| public static void afterClass() throws Exception { | |
| overrideDefaultConfigValue(UserPasswordResetEnabled, "_value", false); | |
| } | |
| @Before | |
| public void setupCommandAvailableChecks() { | |
| apiServer.setApiAccessCheckers(Collections.emptyList()); | |
| } | |
| @After | |
| public void resetCidrConfig() throws Exception { | |
| overrideDefaultConfigValue(ApiServiceConfiguration.ApiSourceCidrChecksEnabled, "_defaultValue", DEFAULT_CIDR_CHECKS_ENABLED); | |
| overrideDefaultConfigValue(ApiServiceConfiguration.ApiAllowedSourceCidrList, "_defaultValue", DEFAULT_ALLOWED_CIDRS); | |
| } | |
| private static void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException { | |
| Field f = ConfigKey.class.getDeclaredField(name); | |
| f.setAccessible(true); | |
| f.set(configKey, o); | |
| } | |
| private void runTestSetupIntegrationPortListenerInvalidPorts(Integer port) { | |
| try (MockedConstruction<ApiServer.ListenerThread> mocked = | |
| Mockito.mockConstruction(ApiServer.ListenerThread.class)) { | |
| apiServer.setupIntegrationPortListener(port); | |
| Assert.assertTrue(mocked.constructed().isEmpty()); | |
| } | |
| } | |
| @Test | |
| public void testSetupIntegrationPortListenerInvalidPorts() { | |
| List<Integer> ports = new ArrayList<>(List.of(-1, -10, 0)); | |
| ports.add(null); | |
| for (Integer port : ports) { | |
| runTestSetupIntegrationPortListenerInvalidPorts(port); | |
| } | |
| } | |
| @Test | |
| public void testSetupIntegrationPortListenerValidPort() { | |
| Integer validPort = 8080; | |
| try (MockedConstruction<ApiServer.ListenerThread> mocked = | |
| Mockito.mockConstruction(ApiServer.ListenerThread.class)) { | |
| apiServer.setupIntegrationPortListener(validPort); | |
| Assert.assertFalse(mocked.constructed().isEmpty()); | |
| ApiServer.ListenerThread listenerThread = mocked.constructed().get(0); | |
| Mockito.verify(listenerThread).start(); | |
| } | |
| } | |
| @Test | |
| public void testForgotPasswordSuccess() { | |
| UserAccount userAccount = mock(UserAccount.class); | |
| Domain domain = mock(Domain.class); | |
| Mockito.when(userAccount.getEmail()).thenReturn("test@test.com"); | |
| Mockito.when(userAccount.getState()).thenReturn("ENABLED"); | |
| Mockito.when(userAccount.getAccountState()).thenReturn("ENABLED"); | |
| Mockito.when(domain.getState()).thenReturn(Domain.State.Active); | |
| Mockito.doNothing().when(userPasswordResetManager).setResetTokenAndSend(userAccount); | |
| Assert.assertTrue(apiServer.forgotPassword(userAccount, domain)); | |
| Mockito.verify(userPasswordResetManager).setResetTokenAndSend(userAccount); | |
| } | |
| @Test(expected = CloudRuntimeException.class) | |
| public void testForgotPasswordFailureNoEmail() { | |
| UserAccount userAccount = mock(UserAccount.class); | |
| Domain domain = mock(Domain.class); | |
| Mockito.when(userAccount.getEmail()).thenReturn(""); | |
| apiServer.forgotPassword(userAccount, domain); | |
| } | |
| @Test(expected = CloudRuntimeException.class) | |
| public void testForgotPasswordFailureDisabledUser() { | |
| UserAccount userAccount = mock(UserAccount.class); | |
| Domain domain = mock(Domain.class); | |
| Mockito.when(userAccount.getEmail()).thenReturn("test@test.com"); | |
| Mockito.when(userAccount.getState()).thenReturn("DISABLED"); | |
| apiServer.forgotPassword(userAccount, domain); | |
| } | |
| @Test(expected = CloudRuntimeException.class) | |
| public void testForgotPasswordFailureDisabledAccount() { | |
| UserAccount userAccount = mock(UserAccount.class); | |
| Domain domain = mock(Domain.class); | |
| Mockito.when(userAccount.getEmail()).thenReturn("test@test.com"); | |
| Mockito.when(userAccount.getState()).thenReturn("ENABLED"); | |
| Mockito.when(userAccount.getAccountState()).thenReturn("DISABLED"); | |
| apiServer.forgotPassword(userAccount, domain); | |
| } | |
| @Test(expected = CloudRuntimeException.class) | |
| public void testForgotPasswordFailureInactiveDomain() { | |
| UserAccount userAccount = mock(UserAccount.class); | |
| Domain domain = mock(Domain.class); | |
| Mockito.when(userAccount.getEmail()).thenReturn("test@test.com"); | |
| Mockito.when(userAccount.getState()).thenReturn("ENABLED"); | |
| Mockito.when(userAccount.getAccountState()).thenReturn("ENABLED"); | |
| Mockito.when(domain.getState()).thenReturn(Domain.State.Inactive); | |
| apiServer.forgotPassword(userAccount, domain); | |
| } | |
| @Test | |
| public void testVerifyApiKeyAccessAllowed() { | |
| Long domainId = 1L; | |
| User user = mock(User.class); | |
| Account account = mock(Account.class); | |
| Mockito.when(user.getApiKeyAccess()).thenReturn(true); | |
| Assert.assertEquals(true, apiServer.verifyApiKeyAccessAllowed(user, account)); | |
| Mockito.verify(account, Mockito.never()).getApiKeyAccess(); | |
| Mockito.when(user.getApiKeyAccess()).thenReturn(false); | |
| Assert.assertEquals(false, apiServer.verifyApiKeyAccessAllowed(user, account)); | |
| Mockito.verify(account, Mockito.never()).getApiKeyAccess(); | |
| Mockito.when(user.getApiKeyAccess()).thenReturn(null); | |
| Mockito.when(account.getApiKeyAccess()).thenReturn(true); | |
| Assert.assertEquals(true, apiServer.verifyApiKeyAccessAllowed(user, account)); | |
| Mockito.when(user.getApiKeyAccess()).thenReturn(null); | |
| Mockito.when(account.getApiKeyAccess()).thenReturn(false); | |
| Assert.assertEquals(false, apiServer.verifyApiKeyAccessAllowed(user, account)); | |
| Mockito.when(user.getApiKeyAccess()).thenReturn(null); | |
| Mockito.when(account.getApiKeyAccess()).thenReturn(null); | |
| Assert.assertEquals(true, apiServer.verifyApiKeyAccessAllowed(user, account)); | |
| } | |
| @Test | |
| public void testLoginUserSuccess() throws Exception { | |
| String username = "user"; | |
| String password = "password"; | |
| Long domainId = 1L; | |
| String domainPath = "/"; | |
| InetAddress loginIp = InetAddress.getByName("127.0.0.1"); | |
| Map<String, Object[]> requestParams = new HashMap<>(); | |
| DomainVO domain = mock(DomainVO.class); | |
| Mockito.when(domain.getId()).thenReturn(domainId); | |
| Mockito.when(domain.getUuid()).thenReturn("domain-uuid"); | |
| Mockito.when(domainManager.findDomainByIdOrPath(domainId, domainPath)).thenReturn(domain); | |
| Mockito.when(domainManager.getDomain(domainId)).thenReturn(domain); | |
| UserAccount userAccount = mock(UserAccount.class); | |
| Mockito.when(userAccount.getId()).thenReturn(100L); | |
| Mockito.when(userAccount.getAccountId()).thenReturn(200L); | |
| Mockito.when(userAccount.getUsername()).thenReturn(username); | |
| Mockito.when(userAccount.getFirstname()).thenReturn("First"); | |
| Mockito.when(userAccount.getLastname()).thenReturn("Last"); | |
| Mockito.when(userAccount.getTimezone()).thenReturn("UTC"); | |
| Mockito.when(userAccount.getRegistrationToken()).thenReturn("token"); | |
| Mockito.when(userAccount.isRegistered()).thenReturn(true); | |
| Mockito.when(userAccount.getDomainId()).thenReturn(domainId); | |
| Map<String, String> userAccDetails = new HashMap<>(); | |
| userAccDetails.put(UserDetailVO.PasswordChangeRequired, "true"); | |
| Mockito.when(userAccount.getDetails()).thenReturn(userAccDetails); | |
| Mockito.when(accountManager.authenticateUser(username, password, domainId, loginIp, requestParams)).thenReturn(userAccount); | |
| Mockito.when(accountManager.clearUserTwoFactorAuthenticationInSetupStateOnLogin(userAccount)).thenReturn(userAccount); | |
| Account account = mock(Account.class); | |
| Mockito.when(account.getAccountName()).thenReturn("account"); | |
| Mockito.when(account.getDomainId()).thenReturn(domainId); | |
| Mockito.when(account.getType()).thenReturn(Account.Type.NORMAL); | |
| Mockito.when(account.getType()).thenReturn(Account.Type.NORMAL); | |
| Mockito.when(accountManager.getAccount(200L)).thenReturn(account); | |
| UserVO userVO = mock(UserVO.class); | |
| Mockito.when(userVO.getUuid()).thenReturn("user-uuid"); | |
| Mockito.when(accountManager.getActiveUser(100L)).thenReturn(userVO); | |
| Mockito.when(session.getAttributeNames()).thenReturn(Collections.enumeration(List.of(PASSWORD_CHANGE_REQUIRED))); | |
| Mockito.when(session.getAttribute(PASSWORD_CHANGE_REQUIRED)).thenReturn(Boolean.TRUE); | |
| ResponseObject response = apiServer.loginUser(session, username, password, domainId, domainPath, loginIp, requestParams); | |
| Assert.assertNotNull(response); | |
| Assert.assertTrue(response instanceof LoginCmdResponse); | |
| Mockito.verify(session).setAttribute(eq("userid"), eq(100L)); | |
| Mockito.verify(session).setAttribute(eq(ApiConstants.SESSIONKEY), anyString()); | |
| } | |
| @Test(expected = CloudAuthenticationException.class) | |
| public void testLoginUserDomainNotFound() throws Exception { | |
| Mockito.when(domainManager.findDomainByIdOrPath(anyLong(), anyString())).thenReturn(null); | |
| apiServer.loginUser(session, "user", "pass", 1L, "/", null, null); | |
| } | |
| @Test(expected = CloudAuthenticationException.class) | |
| public void testLoginUserAuthFailed() throws Exception { | |
| DomainVO domain = mock(DomainVO.class); | |
| Mockito.when(domain.getId()).thenReturn(1L); | |
| Mockito.when(domainManager.findDomainByIdOrPath(anyLong(), anyString())).thenReturn(domain); | |
| Mockito.when(accountManager.authenticateUser(anyString(), anyString(), anyLong(), any(), any())).thenReturn(null); | |
| apiServer.loginUser(session, "user", "pass", 1L, "/", null, null); | |
| } | |
| @Test | |
| public void testCheckCommandAvailableSkipsCidrLookupWhenDisabled() throws Exception { | |
| overrideDefaultConfigValue(ApiServiceConfiguration.ApiSourceCidrChecksEnabled, "_defaultValue", "false"); | |
| User user = Mockito.mock(User.class); | |
| ReflectionTestUtils.invokeMethod(apiServer, "checkCommandAvailable", user, "listVirtualMachines", InetAddress.getByName("127.0.0.1"), null, new ApiKeyPairPermission[0]); | |
| Mockito.verify(accountManager, Mockito.never()).getAccount(Mockito.anyLong()); | |
| } | |
| @Test | |
| public void testCheckCommandAvailableAllowsMatchingCidr() throws Exception { | |
| overrideDefaultConfigValue(ApiServiceConfiguration.ApiSourceCidrChecksEnabled, "_defaultValue", "true"); | |
| overrideDefaultConfigValue(ApiServiceConfiguration.ApiAllowedSourceCidrList, "_defaultValue", "127.0.0.1/32"); | |
| User user = Mockito.mock(User.class); | |
| Mockito.when(user.getAccountId()).thenReturn(1L); | |
| Account account = Mockito.mock(Account.class); | |
| Mockito.when(account.getId()).thenReturn(1L); | |
| Mockito.when(accountManager.getAccount(1L)).thenReturn(account); | |
| ReflectionTestUtils.invokeMethod(apiServer, "checkCommandAvailable", user, "listVirtualMachines", InetAddress.getByName("127.0.0.1"), null, new ApiKeyPairPermission[0]); | |
| Mockito.verify(accountManager).getAccount(1L); | |
| } | |
| @Test(expected = OriginDeniedException.class) | |
| public void testCheckCommandAvailableDeniesNonMatchingCidr() throws Exception { | |
| overrideDefaultConfigValue(ApiServiceConfiguration.ApiSourceCidrChecksEnabled, "_defaultValue", "true"); | |
| overrideDefaultConfigValue(ApiServiceConfiguration.ApiAllowedSourceCidrList, "_defaultValue", "10.0.0.0/8"); | |
| User user = Mockito.mock(User.class); | |
| Mockito.when(user.getAccountId()).thenReturn(1L); | |
| Account account = Mockito.mock(Account.class); | |
| Mockito.when(account.getId()).thenReturn(1L); | |
| Mockito.when(accountManager.getAccount(1L)).thenReturn(account); | |
| ReflectionTestUtils.invokeMethod(apiServer, "checkCommandAvailable", user, "listVirtualMachines", InetAddress.getByName("127.0.0.1"), null, new ApiKeyPairPermission[0]); | |
| } | |
| @Test(expected = PermissionDeniedException.class) | |
| public void testCheckCommandAvailableThrowsWhenUserNull() throws Exception { | |
| ReflectionTestUtils.invokeMethod(apiServer, "checkCommandAvailable", null, "listVirtualMachines", InetAddress.getByName("127.0.0.1"), null, new ApiKeyPairPermission[0]); | |
| } | |
| } |