api,server: apis return their http request type (#11382)

* api,server: apis return their http request type

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix and unit test

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* more test

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* address copilot

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* Update plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java

---------

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
Co-authored-by: dahn <daan@onecht.net>
Co-authored-by: Harikrishna <harikrishna.patnala@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-01-29 13:52:07 +05:30 committed by GitHub
parent 10e0d42f45
commit 9b4f16b73f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 323 additions and 60 deletions

View File

@ -50,4 +50,6 @@ public @interface APICommand {
RoleType[] authorized() default {};
Class<?>[] entityType() default {};
String httpMethod() default "";
}

View File

@ -282,6 +282,7 @@ public class ApiConstants {
public static final String HOST = "host";
public static final String HOST_CONTROL_STATE = "hostcontrolstate";
public static final String HOSTS_MAP = "hostsmap";
public static final String HTTP_REQUEST_TYPE = "httprequesttype";
public static final String HYPERVISOR = "hypervisor";
public static final String INLINE = "inline";
public static final String INSTANCE = "instance";

View File

@ -26,7 +26,8 @@ import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.IsAccountAllowedToCreateOfferingsWithTagsResponse;
@APICommand(name = "isAccountAllowedToCreateOfferingsWithTags", description = "Return true if the specified account is allowed to create offerings with tags.",
responseObject = IsAccountAllowedToCreateOfferingsWithTagsResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
responseObject = IsAccountAllowedToCreateOfferingsWithTagsResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class IsAccountAllowedToCreateOfferingsWithTagsCmd extends BaseCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "Account UUID", required = true)

View File

@ -59,6 +59,10 @@ public class ApiDiscoveryResponse extends BaseResponse {
@Param(description = "Response field type")
private String type;
@SerializedName(ApiConstants.HTTP_REQUEST_TYPE)
@Param(description = "Preferred HTTP request type for the API", since = "4.23.0")
private String httpRequestType;
public ApiDiscoveryResponse() {
params = new HashSet<ApiParameterResponse>();
apiResponse = new HashSet<ApiResponseResponse>();
@ -74,6 +78,7 @@ public class ApiDiscoveryResponse extends BaseResponse {
this.params = new HashSet<>(another.getParams());
this.apiResponse = new HashSet<>(another.getApiResponse());
this.type = another.getType();
this.httpRequestType = another.getHttpRequestType();
this.setObjectName(another.getObjectName());
}
@ -140,4 +145,12 @@ public class ApiDiscoveryResponse extends BaseResponse {
public String getType() {
return type;
}
public String getHttpRequestType() {
return httpRequestType;
}
public void setHttpRequestType(String httpRequestType) {
this.httpRequestType = httpRequestType;
}
}

View File

@ -50,6 +50,7 @@ import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.api.ApiServlet;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.serializer.Param;
import com.cloud.user.Account;
@ -189,7 +190,7 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
return responseResponse;
}
private ApiDiscoveryResponse getCmdRequestMap(Class<?> cmdClass, APICommand apiCmdAnnotation) {
protected ApiDiscoveryResponse getCmdRequestMap(Class<?> cmdClass, APICommand apiCmdAnnotation) {
String apiName = apiCmdAnnotation.name();
ApiDiscoveryResponse response = new ApiDiscoveryResponse();
response.setName(apiName);
@ -197,6 +198,12 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
if (!apiCmdAnnotation.since().isEmpty()) {
response.setSince(apiCmdAnnotation.since());
}
String httpRequestType = apiCmdAnnotation.httpMethod();
if (StringUtils.isBlank(httpRequestType)) {
httpRequestType = ApiServlet.GET_REQUEST_COMMANDS.matcher(apiName.toLowerCase()).matches() ?
"GET" : "POST";
}
response.setHttpRequestType(httpRequestType);
Set<Field> fields = ReflectUtil.getAllFieldsForClass(cmdClass, new Class<?>[] {BaseCmd.class, BaseAsyncCmd.class, BaseAsyncCreateCmd.class});

View File

@ -0,0 +1,123 @@
// 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 org.apache.cloudstack.discovery;
import static org.mockito.ArgumentMatchers.any;
import java.lang.reflect.Field;
import java.util.Set;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserCmd;
import org.apache.cloudstack.api.command.user.discovery.ListApisCmd;
import org.apache.cloudstack.api.response.ApiDiscoveryResponse;
import org.apache.cloudstack.api.response.ApiParameterResponse;
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.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.utils.ReflectUtil;
@RunWith(MockitoJUnitRunner.class)
public class ApiDiscoveryServiceImplTest {
@Mock
APICommand apiCommandMock;
@Spy
@InjectMocks
ApiDiscoveryServiceImpl discoveryServiceSpy;
@Before
public void setUp() {
Mockito.when(apiCommandMock.name()).thenReturn("listApis");
Mockito.when(apiCommandMock.since()).thenReturn("");
}
@Test
public void getCmdRequestMapReturnsResponseWithCorrectApiNameAndDescription() {
Mockito.when(apiCommandMock.description()).thenReturn("Lists all APIs");
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(ListApisCmd.class, apiCommandMock);
Assert.assertEquals("listApis", response.getName());
Assert.assertEquals("Lists all APIs", response.getDescription());
}
@Test
public void getCmdRequestMapSetsHttpRequestTypeToGetWhenApiNameMatchesGetPattern() {
Mockito.when(apiCommandMock.name()).thenReturn("getUser");
Mockito.when(apiCommandMock.httpMethod()).thenReturn("");
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(GetUserCmd.class, apiCommandMock);
Assert.assertEquals("GET", response.getHttpRequestType());
}
@Test
public void getCmdRequestMapSetsHttpRequestTypeToPostWhenApiNameDoesNotMatchGetPattern() {
Mockito.when(apiCommandMock.name()).thenReturn("createAccount");
Mockito.when(apiCommandMock.httpMethod()).thenReturn("");
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(CreateAccountCmd.class, apiCommandMock);
Assert.assertEquals("POST", response.getHttpRequestType());
}
@Test
public void getCmdRequestMapSetsAsyncToTrueForAsyncCommand() {
Mockito.when(apiCommandMock.name()).thenReturn("asyncApi");
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(BaseAsyncCmd.class, apiCommandMock);
Assert.assertTrue(response.getAsync());
}
@Test
public void getCmdRequestMapDoesNotAddParamsWithoutParameterAnnotation() {
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(BaseCmd.class, apiCommandMock);
Assert.assertFalse(response.getParams().isEmpty());
Assert.assertEquals(1, response.getParams().size());
}
@Test
public void getCmdRequestMapAddsParamsWithExposedAndIncludedInApiDocAnnotations() {
Field fieldMock = Mockito.mock(Field.class);
Parameter parameterMock = Mockito.mock(Parameter.class);
Mockito.when(parameterMock.expose()).thenReturn(true);
Mockito.when(parameterMock.includeInApiDoc()).thenReturn(true);
Mockito.when(parameterMock.name()).thenReturn("paramName");
Mockito.when(parameterMock.since()).thenReturn("");
Mockito.when(parameterMock.entityType()).thenReturn(new Class[]{Object.class});
Mockito.when(parameterMock.description()).thenReturn("paramDescription");
Mockito.when(parameterMock.type()).thenReturn(BaseCmd.CommandType.STRING);
Mockito.when(fieldMock.getAnnotation(Parameter.class)).thenReturn(parameterMock);
try (MockedStatic<ReflectUtil> reflectUtilMockedStatic = Mockito.mockStatic(ReflectUtil.class)) {
reflectUtilMockedStatic.when(() -> ReflectUtil.getAllFieldsForClass(any(Class.class), any(Class[].class)))
.thenReturn(Set.of(fieldMock));
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(ListApisCmd.class, apiCommandMock);
Set<ApiParameterResponse> params = response.getParams();
Assert.assertEquals(1, params.size());
ApiParameterResponse paramResponse = params.iterator().next();
Assert.assertEquals("paramName", ReflectionTestUtils.getField(paramResponse, "name"));
}
}
}

View File

@ -35,7 +35,8 @@ import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.api.response.QuotaStatementItemResponse;
@APICommand(name = "quotaBalance", responseObject = QuotaStatementItemResponse.class, description = "Create a quota balance statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@APICommand(name = "quotaBalance", responseObject = QuotaStatementItemResponse.class, description = "Create a quota balance statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class QuotaBalanceCmd extends BaseCmd {

View File

@ -26,7 +26,8 @@ import org.apache.cloudstack.quota.QuotaService;
import javax.inject.Inject;
@APICommand(name = "quotaIsEnabled", responseObject = QuotaEnabledResponse.class, description = "Return true if the plugin is enabled", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@APICommand(name = "quotaIsEnabled", responseObject = QuotaEnabledResponse.class, description = "Return true if the plugin is enabled", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class QuotaEnabledCmd extends BaseCmd {

View File

@ -35,7 +35,8 @@ import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import com.cloud.user.Account;
@APICommand(name = "quotaStatement", responseObject = QuotaStatementItemResponse.class, description = "Create a quota statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@APICommand(name = "quotaStatement", responseObject = QuotaStatementItemResponse.class, description = "Create a quota statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class QuotaStatementCmd extends BaseCmd {

View File

@ -33,7 +33,8 @@ import java.util.List;
import javax.inject.Inject;
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists balance and quota usage for all Accounts", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists balance and quota usage for all Accounts", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class QuotaSummaryCmd extends BaseListCmd {
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = false, description = "Optional, Account Id for which statement needs to be generated")

View File

@ -38,7 +38,8 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@APICommand(name = "quotaTariffList", responseObject = QuotaTariffResponse.class, description = "Lists all quota tariff plans", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@APICommand(name = "quotaTariffList", responseObject = QuotaTariffResponse.class, description = "Lists all quota tariff plans", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class QuotaTariffListCmd extends BaseListCmd {
@Inject

View File

@ -31,7 +31,8 @@ import com.cloud.user.Account;
responseObject = CloudianEnabledResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.11.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
httpMethod = "GET")
public class CloudianIsEnabledCmd extends BaseCmd {
@Inject

View File

@ -26,7 +26,8 @@ import com.cloud.user.Account;
description = "Returns the status of CloudStack, whether a shutdown has been triggered and if ready to shutdown",
since = "4.19.0",
responseObject = ManagementServerMaintenanceResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class ReadyForShutdownCmd extends BaseMSMaintenanceActionCmd {
public static final String APINAME = "readyForShutdown";

View File

@ -20,8 +20,10 @@ import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import com.cloud.api.response.ApiResponseSerializer;
import com.cloud.user.Account;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
@ -37,13 +39,13 @@ import org.apache.cloudstack.oauth2.OAuth2AuthManager;
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
import org.apache.commons.lang.ArrayUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.cloud.api.response.ApiResponseSerializer;
import com.cloud.user.Account;
@APICommand(name = "verifyOAuthCodeAndGetUser", description = "Verify the OAuth Code and fetch the corresponding user from provider", responseObject = OauthProviderResponse.class, entityType = {},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0")
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0",
httpMethod = "GET")
public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuthenticator {
/////////////////////////////////////////////////////

View File

@ -25,8 +25,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.Set;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.servlet.ServletConfig;
@ -52,10 +52,9 @@ import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.managed.context.ManagedContext;
import org.apache.cloudstack.utils.consoleproxy.ConsoleAccessUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.commons.lang3.EnumUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
@ -70,12 +69,12 @@ import com.cloud.user.AccountManagerImpl;
import com.cloud.user.AccountService;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.utils.HttpUtils;
import com.cloud.utils.HttpUtils.ApiSessionKeySameSite;
import com.cloud.utils.HttpUtils.ApiSessionKeyCheckOption;
import com.cloud.utils.HttpUtils.ApiSessionKeySameSite;
import com.cloud.utils.StringUtils;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
@Component("apiServlet")
@ -84,9 +83,7 @@ public class ApiServlet extends HttpServlet {
private static final Logger ACCESSLOGGER = LogManager.getLogger("apiserver." + ApiServlet.class.getName());
private static final String REPLACEMENT = "_";
private static final String LOGGER_REPLACEMENTS = "[\n\r\t]";
private static final Pattern GET_REQUEST_COMMANDS = Pattern.compile("^(get|list|query|find)(\\w+)+$");
private static final HashSet<String> GET_REQUEST_COMMANDS_LIST = new HashSet<>(Set.of("isaccountallowedtocreateofferingswithtags",
"readyforshutdown", "cloudianisenabled", "quotabalance", "quotasummary", "quotatarifflist", "quotaisenabled", "quotastatement", "verifyoauthcodeandgetuser"));
public static final Pattern GET_REQUEST_COMMANDS = Pattern.compile("^(get|list|query|find)(\\w+)+$");
private static final HashSet<String> POST_REQUESTS_TO_DISABLE_LOGGING = new HashSet<>(Set.of(
"login",
"oauthlogin",
@ -367,7 +364,7 @@ public class ApiServlet extends HttpServlet {
}
}
if (apiServer.isPostRequestsAndTimestampsEnforced() && !isStateChangingCommandUsingPOST(command, req.getMethod(), params)) {
if (apiServer.isPostRequestsAndTimestampsEnforced() && isStateChangingCommandNotUsingPOST(command, req.getMethod(), params)) {
String errorText = String.format("State changing command %s needs to be sent using POST request", command);
if (command.equalsIgnoreCase("updateConfiguration") && params.containsKey("name")) {
errorText = String.format("Changes for configuration %s needs to be sent using POST request", params.get("name")[0]);
@ -485,13 +482,32 @@ public class ApiServlet extends HttpServlet {
return verify2FA;
}
private boolean isStateChangingCommandUsingPOST(String command, String method, Map<String, Object[]> params) {
if (command == null || (!GET_REQUEST_COMMANDS.matcher(command.toLowerCase()).matches() && !GET_REQUEST_COMMANDS_LIST.contains(command.toLowerCase())
&& !command.equalsIgnoreCase("updateConfiguration") && !method.equals("POST"))) {
protected boolean isStateChangingCommandNotUsingPOST(String command, String method, Map<String, Object[]> params) {
if (BaseCmd.HTTPMethod.POST.toString().equalsIgnoreCase(method)) {
return false;
}
return !command.equalsIgnoreCase("updateConfiguration") || method.equals("POST") || (params.containsKey("name")
&& params.get("name")[0].toString().equalsIgnoreCase(ApiServer.EnforcePostRequestsAndTimestamps.key()));
if (command == null || method == null) {
return true;
}
String commandHttpMethod = null;
try {
Class<?> cmdClass = apiServer.getCmdClass(command);
if (cmdClass != null) {
APICommand at = cmdClass.getAnnotation(APICommand.class);
if (at != null && org.apache.commons.lang3.StringUtils.isNotBlank(at.httpMethod())) {
commandHttpMethod = at.httpMethod();
}
}
} catch (CloudRuntimeException e) {
LOGGER.trace("Command class not found for {}; falling back to pattern match", command, e);
}
if (BaseCmd.HTTPMethod.GET.toString().equalsIgnoreCase(commandHttpMethod) ||
GET_REQUEST_COMMANDS.matcher(command.toLowerCase()).matches()) {
return false;
}
return !command.equalsIgnoreCase("updateConfiguration") ||
!params.containsKey("name") ||
!ApiServer.EnforcePostRequestsAndTimestamps.key().equalsIgnoreCase(params.get("name")[0].toString());
}
protected boolean skip2FAcheckForAPIs(String command) {

View File

@ -16,36 +16,8 @@
// under the License.
package com.cloud.api;
import com.cloud.api.auth.ListUserTwoFactorAuthenticatorProvidersCmd;
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
import com.cloud.api.auth.ValidateUserTwoFactorAuthenticationCodeCmd;
import com.cloud.server.ManagementServer;
import com.cloud.user.Account;
import com.cloud.user.AccountManagerImpl;
import com.cloud.user.AccountService;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.utils.HttpUtils;
import com.cloud.vm.UserVmManager;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.auth.APIAuthenticationManager;
import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.api.auth.APIAuthenticator;
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
import org.junit.After;
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.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -57,11 +29,46 @@ import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.ArgumentMatchers.nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.auth.APIAuthenticationManager;
import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.api.auth.APIAuthenticator;
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
import org.apache.cloudstack.api.command.admin.offering.IsAccountAllowedToCreateOfferingsWithTagsCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
import org.junit.After;
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.junit.MockitoJUnitRunner;
import com.cloud.api.auth.ListUserTwoFactorAuthenticatorProvidersCmd;
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
import com.cloud.api.auth.ValidateUserTwoFactorAuthenticationCodeCmd;
import com.cloud.server.ManagementServer;
import com.cloud.user.Account;
import com.cloud.user.AccountManagerImpl;
import com.cloud.user.AccountService;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.utils.HttpUtils;
import com.cloud.vm.UserVmManager;
@RunWith(MockitoJUnitRunner.class)
public class ApiServletTest {
private static final String[] STATE_CHANGING_COMMAND_CHECK_NAME_PARAM =
{ApiServer.EnforcePostRequestsAndTimestamps.key()};
@Mock
ApiServer apiServer;
@ -461,4 +468,88 @@ public class ApiServletTest {
Assert.assertEquals(false, result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsFalseForPostMethod() {
String command = "updateConfiguration";
String method = "POST";
Map<String, Object[]> params = new HashMap<>();
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertFalse(result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsTrueForNullCommandAndMethod() {
String command = null;
String method = null;
Map<String, Object[]> params = new HashMap<>();
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertTrue(result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsFalseForGetHttpMethodAnnotation() {
String command = "isAccountAllowedToCreateOfferingsWithTags";
String method = "GET";
Map<String, Object[]> params = new HashMap<>();
Class<?> cmdClass = IsAccountAllowedToCreateOfferingsWithTagsCmd.class;
APICommand apiCommand = cmdClass.getAnnotation(APICommand.class);
Mockito.doReturn(cmdClass).when(apiServer).getCmdClass(command);
Assert.assertNotNull(apiCommand);
Assert.assertEquals("GET", apiCommand.httpMethod());
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertFalse(result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsFalseForMatchingGetRequestPattern() {
String command = "listZones";
String method = "GET";
Map<String, Object[]> params = new HashMap<>();
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertFalse(result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsTrueForMissingNameParameter() {
String command = "updateConfiguration";
String method = "GET";
Map<String, Object[]> params = new HashMap<>();
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertTrue(result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsFalseForUpdateConfigurationEnforcePostRequestsKey() {
String command = "updateConfiguration";
String method = "GET";
Map<String, Object[]> params = new HashMap<>();
params.put("name", STATE_CHANGING_COMMAND_CHECK_NAME_PARAM);
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertFalse(result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsFalseForWrongApiEnforcePostRequestsKey() {
String command = "updateSomeApi";
String method = "GET";
Map<String, Object[]> params = new HashMap<>();
params.put("name", STATE_CHANGING_COMMAND_CHECK_NAME_PARAM);
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertTrue(result);
}
@Test
public void isStateChangingCommandNotUsingPOSTReturnsFalseForUpdateConfigurationNonEnforcePostRequestsKey() {
String command = "updateConfiguration";
String method = "GET";
Map<String, Object[]> params = new HashMap<>();
params.put("name", new String[] { "key" });
boolean result = servlet.isStateChangingCommandNotUsingPOST(command, method, params);
Assert.assertTrue(result);
}
}