Skip to content

Commit

Permalink
check user case creation limit
Browse files Browse the repository at this point in the history
Signed-off-by: Abdelsalem <[email protected]>
  • Loading branch information
AbdelHedhili committed Jun 11, 2024
1 parent 239906a commit 8913cc2
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public ResponseEntity<Void> createStudy(@PathVariable("studyName") String studyN
@RequestParam(QUERY_PARAM_PARENT_DIRECTORY_ID) UUID parentDirectoryUuid,
@RequestHeader("userId") String userId,
@RequestBody(required = false) Map<String, Object> importParams) {
exploreService.assertCanCreateCase(userId);
CaseInfo caseInfo = new CaseInfo(caseUuid, caseFormat);
exploreService.createStudy(studyName, caseInfo, description, userId, parentDirectoryUuid, importParams, duplicateCase);
return ResponseEntity.ok().build();
Expand All @@ -67,6 +68,7 @@ public ResponseEntity<Void> createStudy(@PathVariable("studyName") String studyN
public ResponseEntity<Void> duplicateStudy(@RequestParam("duplicateFrom") UUID studyId,
@RequestParam(name = QUERY_PARAM_PARENT_DIRECTORY_ID, required = false) UUID targetDirectoryId,
@RequestHeader("userId") String userId) {
exploreService.assertCanCreateCase(userId);
exploreService.duplicateStudy(studyId, targetDirectoryId, userId);
return ResponseEntity.ok().build();
}
Expand All @@ -79,6 +81,7 @@ public ResponseEntity<Void> createCase(@PathVariable("caseName") String caseName
@RequestParam("description") String description,
@RequestParam(QUERY_PARAM_PARENT_DIRECTORY_ID) UUID parentDirectoryUuid,
@RequestHeader("userId") String userId) {
exploreService.assertCanCreateCase(userId);
exploreService.createCase(caseName, caseFile, description, userId, parentDirectoryUuid);
return ResponseEntity.ok().build();
}
Expand All @@ -90,6 +93,7 @@ public ResponseEntity<Void> duplicateCase(
@RequestParam("duplicateFrom") UUID caseId,
@RequestParam(name = QUERY_PARAM_PARENT_DIRECTORY_ID, required = false) UUID targetDirectoryId,
@RequestHeader("userId") String userId) {
exploreService.assertCanCreateCase(userId);
exploreService.duplicateCase(caseId, targetDirectoryId, userId);
return ResponseEntity.ok().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public enum Type {
UNKNOWN_ELEMENT_TYPE,
REMOTE_ERROR,
IMPORT_CASE_FAILED,
INCORRECT_CASE_FILE
INCORRECT_CASE_FILE,
MAX_ELEMENTS_EXCEEDED,
}

private final Type type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ protected ResponseEntity<Object> handleExploreException(ExploreException excepti
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(exception.getMessage());
case UNKNOWN_ELEMENT_TYPE:
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(UNKNOWN_ELEMENT_TYPE);
case MAX_ELEMENTS_EXCEEDED:
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(MAX_ELEMENTS_EXCEEDED);
default:
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.stream.Collectors;

import static org.gridsuite.explore.server.ExploreException.Type.*;
import static org.gridsuite.explore.server.utils.ExploreUtils.wrapRemoteError;

@Service
public class CaseService implements IDirectoryElementsService {
Expand All @@ -43,14 +44,6 @@ public CaseService(@Value("${powsybl.services.case-server.base-uri:http://case-s
this.restTemplate = restTemplate;
}

private static ExploreException wrapRemoteError(String response, HttpStatusCode statusCode) {
if (!"".equals(response)) {
throw new ExploreException(ExploreException.Type.REMOTE_ERROR, response);
} else {
throw new ExploreException(ExploreException.Type.REMOTE_ERROR, "{\"message\": " + statusCode + "\"}");
}
}

public void setBaseUri(String actionsServerBaseUri) {
this.caseServerBaseUri = actionsServerBaseUri;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,19 @@ private List<ElementAttributes> getElementsInfos(List<UUID> elementsUuids, List<
}
}

public int getUserCasesCount(String userId) {
String path = UriComponentsBuilder
.fromPath(DELIMITER + DIRECTORY_SERVER_API_VERSION + DELIMITER + "users/{userId}/cases/count")
.buildAndExpand(userId)
.toUriString();

Integer casesCount = restTemplate.exchange(directoryServerBaseUri + path, HttpMethod.GET, null, Integer.class).getBody();
if (casesCount == null) {
throw new ExploreException(REMOTE_ERROR, "Could not get cases count");
}
return casesCount;
}

public void notifyDirectoryChanged(UUID elementUuid, String userId) {
String path = UriComponentsBuilder
.fromPath(ELEMENTS_SERVER_ROOT_PATH + "/{elementUuid}/notification?type={update_directory}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import java.util.Map;
import java.util.UUID;

import static org.gridsuite.explore.server.ExploreException.Type.NOT_ALLOWED;
import static org.gridsuite.explore.server.ExploreException.Type.UNKNOWN_ELEMENT_TYPE;
import static org.gridsuite.explore.server.ExploreException.Type.*;


/**
Expand All @@ -47,15 +46,16 @@ public class ExploreService {
private final ParametersService parametersService;

private static final Logger LOGGER = LoggerFactory.getLogger(ExploreService.class);
private final UserAdminService userAdminService;

public ExploreService(
DirectoryService directoryService,
StudyService studyService,
ContingencyListService contingencyListService,
FilterService filterService,
NetworkModificationService networkModificationService,
CaseService caseService,
ParametersService parametersService) {
DirectoryService directoryService,
StudyService studyService,
ContingencyListService contingencyListService,
FilterService filterService,
NetworkModificationService networkModificationService,
CaseService caseService,
ParametersService parametersService, UserAdminService userAdminService) {

this.directoryService = directoryService;
this.studyService = studyService;
Expand All @@ -64,6 +64,7 @@ public ExploreService(
this.networkModificationService = networkModificationService;
this.caseService = caseService;
this.parametersService = parametersService;
this.userAdminService = userAdminService;
}

public void createStudy(String studyName, CaseInfo caseInfo, String description, String userId, UUID parentDirectoryUuid, Map<String, Object> importParams, Boolean duplicateCase) {
Expand Down Expand Up @@ -275,4 +276,14 @@ public void duplicateNetworkModifications(UUID sourceId, UUID parentDirectoryUui
// create corresponding directory element
directoryService.duplicateElement(sourceId, newNetworkModification, parentDirectoryUuid, userId);
}

public void assertCanCreateCase(String userId) {
Integer userMaxAllowedStudiesAndCases = userAdminService.getUserMaxAllowedCases(userId);
if (userMaxAllowedStudiesAndCases != null) {
int userCasesCount = directoryService.getUserCasesCount(userId);
if (userCasesCount >= userMaxAllowedStudiesAndCases) {
throw new ExploreException(MAX_ELEMENTS_EXCEEDED);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.explore.server.services;

import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import static org.gridsuite.explore.server.utils.ExploreUtils.wrapRemoteError;

/**
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com>
*/
@Service
public class UserAdminService {

private static final String USER_ADMIN_API_VERSION = "v1";
private static final String USERS_MAX_ALLOWED_CASES_URI = "/users/{sub}/profile/max-cases";

private static final String DELIMITER = "/";
private final RestTemplate restTemplate;
@Setter
private String userAdminServerBaseUri;

@Autowired
public UserAdminService(RestTemplate restTemplate, RemoteServicesProperties remoteServicesProperties) {
this.userAdminServerBaseUri = remoteServicesProperties.getServiceUri("user-admin-server");
this.restTemplate = restTemplate;
}

public Integer getUserMaxAllowedCases(String sub) {
String path = UriComponentsBuilder.fromPath(DELIMITER + USER_ADMIN_API_VERSION + USERS_MAX_ALLOWED_CASES_URI)
.buildAndExpand(sub).toUriString();
try {
return restTemplate.getForObject(userAdminServerBaseUri + path, Integer.class);
} catch (HttpStatusCodeException e) {
if (e.getStatusCode().value() == 404) {
return null; // no profile == unlimited import
}
throw wrapRemoteError(e.getMessage(), e.getStatusCode());

}
}

}
29 changes: 29 additions & 0 deletions src/main/java/org/gridsuite/explore/server/utils/ExploreUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.explore.server.utils;

import org.gridsuite.explore.server.ExploreException;
import org.springframework.http.HttpStatusCode;

/**
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com
*/
public final class ExploreUtils {

private ExploreUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

public static ExploreException wrapRemoteError(String response, HttpStatusCode statusCode) {
if (!"".equals(response)) {
throw new ExploreException(ExploreException.Type.REMOTE_ERROR, response);
} else {
throw new ExploreException(ExploreException.Type.REMOTE_ERROR, "{\"message\": " + statusCode + "\"}");
}
}

}
3 changes: 3 additions & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ gridsuite:
-
name: sensitivity-analysis-server
base-uri: http://localhost:5030
-
name: user-admin-server
base-uri: http://localhost:5033

63 changes: 61 additions & 2 deletions src/test/java/org/gridsuite/explore/server/ExploreTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
import java.util.*;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.gridsuite.explore.server.ExploreException.Type.MAX_ELEMENTS_EXCEEDED;
import static org.junit.Assert.*;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
Expand Down Expand Up @@ -83,6 +83,7 @@ public class ExploreTest {
private static final String STUDY_ERROR_NAME = "studyInError";
private static final String STUDY1 = "study1";
private static final String USER1 = "user1";
private static final String USER_WITH_CASE_LIMIT = "limitedUser";
public static final String FILTER_CONTINGENCY_LIST = "filterContingencyList";
public static final String FILTER_CONTINGENCY_LIST_2 = "filterContingencyList2";
public static final String FILTER = "FILTER";
Expand Down Expand Up @@ -112,6 +113,8 @@ public class ExploreTest {
@Autowired
private ObjectMapper mapper;
private MockWebServer server;
@Autowired
private UserAdminService userAdminService;

@Before
public void setup() throws IOException {
Expand All @@ -128,6 +131,7 @@ public void setup() throws IOException {
contingencyListService.setActionsServerBaseUri(baseUrl);
networkModificationService.setNetworkModificationServerBaseUri(baseUrl);
caseService.setBaseUri(baseUrl);
userAdminService.setUserAdminServerBaseUri(baseUrl);
remoteServicesProperties.getServices().forEach(s -> s.setBaseUri(baseUrl));

String privateStudyAttributesAsString = mapper.writeValueAsString(new ElementAttributes(PRIVATE_STUDY_UUID, STUDY1, "STUDY", USER1, 0, null));
Expand Down Expand Up @@ -299,6 +303,18 @@ public MockResponse dispatch(RecordedRequest request) {
} else if (path.matches("/v1/studies/metadata[?]ids=" + PRIVATE_STUDY_UUID)) {
return new MockResponse().setBody(listOfPrivateStudyAttributesAsString.replace("elementUuid", "id")).setResponseCode(200)
.addHeader("Content-Type", "application/json; charset=utf-8");
} else if (path.matches("/v1/users/" + USER_WITH_CASE_LIMIT + "/profile/max-cases")) {
return new MockResponse().setBody("3").setResponseCode(200)
.addHeader("Content-Type", "application/json; charset=utf-8");
} else if (path.matches("/v1/users/.*/profile/max-cases")) {
return new MockResponse().setResponseCode(200)
.addHeader("Content-Type", "application/json; charset=utf-8");
} else if (path.matches("/v1/users/" + USER_WITH_CASE_LIMIT + "/cases/count")) {
return new MockResponse().setBody("4").setResponseCode(200)
.addHeader("Content-Type", "application/json; charset=utf-8");
} else if (path.matches("/v1/users/.*/cases/count")) {
return new MockResponse().setBody("0").setResponseCode(200)
.addHeader("Content-Type", "application/json; charset=utf-8");
}
} else if ("DELETE".equals(request.getMethod())) {
if (path.matches("/v1/filters/" + FILTER_UUID)) {
Expand Down Expand Up @@ -752,4 +768,47 @@ public void testGetModificationMetadata() {
assertEquals(1, elementsMetadata.size());
assertEquals(mapper.writeValueAsString(elementsMetadata.get(0)), expectedResult);
}

@Test
public void testMaxCaseCreationExceeded() throws Exception {

//test create a study with a user that already exceeded his cases limit
MvcResult result = mockMvc.perform(post("/v1/explore/studies/" + STUDY1 + "/cases/" + CASE_UUID + "?description=desc&parentDirectoryUuid=" + PARENT_DIRECTORY_UUID)
.param("duplicateCase", "false")
.header("userId", USER_WITH_CASE_LIMIT)
.param("caseFormat", "XIIDM")
.contentType(APPLICATION_JSON)
).andExpect(status().isForbidden())
.andReturn();
assertTrue(result.getResponse().getContentAsString().contains(MAX_ELEMENTS_EXCEEDED.name()));

//test duplicate a study with a user that already exceeded his cases limit
result = mockMvc.perform(post("/v1/explore/studies?duplicateFrom={studyUuid}&parentDirectoryUuid={parentDirectoryUuid}",
PUBLIC_STUDY_UUID, PARENT_DIRECTORY_UUID)
.header("userId", USER_WITH_CASE_LIMIT)
).andExpect(status().isForbidden())
.andReturn();
assertTrue(result.getResponse().getContentAsString().contains(MAX_ELEMENTS_EXCEEDED.name()));

//test duplicate a case with a user that already exceeded his cases limit
result = mockMvc.perform(post("/v1/explore/cases?duplicateFrom={caseUuid}&parentDirectoryUuid={parentDirectoryUuid}",
CASE_UUID, PARENT_DIRECTORY_UUID).header("userId", USER_WITH_CASE_LIMIT))
.andExpect(status().isForbidden())
.andReturn();
assertTrue(result.getResponse().getContentAsString().contains(MAX_ELEMENTS_EXCEEDED.name()));

//test create a case with a user that already exceeded his cases limit
try (InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:" + TEST_FILE))) {
MockMultipartFile mockFile = new MockMultipartFile("caseFile", TEST_FILE, "text/xml", is);

result = mockMvc.perform(multipart("/v1/explore/cases/{caseName}?description={description}&parentDirectoryUuid={parentDirectoryUuid}",
STUDY1, "description", PARENT_DIRECTORY_UUID).file(mockFile)
.header("userId", USER_WITH_CASE_LIMIT)
.contentType(MediaType.MULTIPART_FORM_DATA)
)
.andExpect(status().isForbidden())
.andReturn();
assertTrue(result.getResponse().getContentAsString().contains(MAX_ELEMENTS_EXCEEDED.name()));
}
}
}

0 comments on commit 8913cc2

Please sign in to comment.