mirror of https://github.com/apache/cloudstack.git
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:
parent
10e0d42f45
commit
9b4f16b73f
|
|
@ -50,4 +50,6 @@ public @interface APICommand {
|
|||
RoleType[] authorized() default {};
|
||||
|
||||
Class<?>[] entityType() default {};
|
||||
|
||||
String httpMethod() default "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue