diff --git a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/controllers/AccountApiController.java b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/controllers/AccountApiController.java index d778517e..6d3cbdd3 100644 --- a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/controllers/AccountApiController.java +++ b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/controllers/AccountApiController.java @@ -18,11 +18,14 @@ package tools.dynamia.modules.saas.controllers; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; import tools.dynamia.domain.query.QueryConditions; import tools.dynamia.domain.query.QueryParameters; import tools.dynamia.domain.services.AbstractService; +import tools.dynamia.modules.saas.api.AccountServiceAPI; import tools.dynamia.modules.saas.api.AccountStatsList; import tools.dynamia.modules.saas.api.dto.AccountDTO; import tools.dynamia.modules.saas.api.enums.AccountPeriodicity; @@ -37,6 +40,7 @@ import java.time.LocalDateTime; import java.util.Date; +import java.util.Map; @RestController @RequestMapping(value = "/api/saas", produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @@ -53,15 +57,20 @@ public class AccountApiController extends AbstractService { } private final AccountService service; + private final AccountServiceAPI serviceAPI; @Autowired - public AccountApiController(AccountService service) { + public AccountApiController(AccountService service, AccountServiceAPI serviceAPI) { this.service = service; + this.serviceAPI = serviceAPI; } @GetMapping("/account/{uuid}") - public AccountDTO getAccount(@PathVariable("uuid") String uuid, HttpServletRequest request) { + public ResponseEntity getAccount(@PathVariable("uuid") String uuid, HttpServletRequest request) { + if (!isAuthorized(request) && !isSameAccount(uuid, request)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } Account account = getAccount(uuid); @@ -75,28 +84,31 @@ public AccountDTO getAccount(@PathVariable("uuid") String uuid, HttpServletReque accountDTO.setStatusDescription("Licencia invalida"); } } - return accountDTO; + return ResponseEntity.ok(accountDTO); } - return NO_ACCOUNT; + return ResponseEntity.ok(NO_ACCOUNT); } @PostMapping("/account/{uuid}/stats") - public String updateStats(@PathVariable("uuid") String uuid, @RequestBody AccountStatsList stats, HttpServletRequest request) { + public ResponseEntity> updateStats(@PathVariable("uuid") String uuid, @RequestBody AccountStatsList stats, HttpServletRequest request) { + if (!isAuthorized(request) && !isSameAccount(uuid, request)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } Account account = getAccount(uuid); if (account != null) { if (stats != null) { service.updateStats(account, stats.getData()); - return "DONE: Account stats updated"; + return ResponseEntity.ok(Map.of("message", "DONE: Account stats updated")); } else { - return "Invalid stats"; + return ResponseEntity.ok(Map.of("message", "Invalid stats")); } } else { - return "Cannot found account with uuid: " + uuid; + return ResponseEntity.notFound().build(); } } @@ -110,7 +122,12 @@ private Account getAccount(@PathVariable("uuid") String uuid) { } @GetMapping("/account/{uuid}/parameter/{name}") - public String getAccountParameter(@PathVariable("uuid") String uuid, @PathVariable("name") String name, HttpServletRequest request) { + public ResponseEntity> getAccountParameter(@PathVariable("uuid") String uuid, @PathVariable("name") String name, HttpServletRequest request) { + + if (!isAuthorized(request) && !isSameAccount(uuid, request)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + String defautlValue = request.getParameter("defaultValue"); String value = null; Account account = getAccount(uuid); @@ -129,7 +146,11 @@ public String getAccountParameter(@PathVariable("uuid") String uuid, @PathVariab }); } } - return value; + if (value != null) { + return ResponseEntity.ok(Map.of("parameter", name, "value", value)); + } else { + return ResponseEntity.notFound().build(); + } } private void newLog(String uuid, HttpServletRequest request, Account account) { @@ -142,4 +163,18 @@ private void newLog(String uuid, HttpServletRequest request, Account account) { } } + + public boolean isAuthorized(HttpServletRequest request) { + if (serviceAPI.getSystemAccountId().equals(serviceAPI.getCurrentAccountId())) { + return true; + } + + return false; + } + + public boolean isSameAccount(String uuid, HttpServletRequest request) { + var subdomain = HttpUtils.getSubdomain(request); + var currentAccount = service.getAccount(subdomain); + return uuid.equals(currentAccount.getUuid()); + } } diff --git a/platform/app/src/main/java/tools/dynamia/app/controllers/AbstractCrudServiceRestController.java b/platform/app/src/main/java/tools/dynamia/app/controllers/AbstractCrudServiceRestController.java index b8e07bec..7374dd9b 100644 --- a/platform/app/src/main/java/tools/dynamia/app/controllers/AbstractCrudServiceRestController.java +++ b/platform/app/src/main/java/tools/dynamia/app/controllers/AbstractCrudServiceRestController.java @@ -4,10 +4,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import tools.dynamia.commons.SimpleCache; import tools.dynamia.commons.StringPojoParser; import tools.dynamia.domain.ValidationError; import tools.dynamia.domain.query.QueryParameters; import tools.dynamia.domain.services.CrudService; +import tools.dynamia.integration.Containers; +import tools.dynamia.viewers.ViewDescriptorFactory; import tools.jackson.core.JacksonException; import tools.jackson.databind.json.JsonMapper; @@ -46,8 +49,11 @@ public abstract class AbstractCrudServiceRestController { */ private final JsonMapper mapper = StringPojoParser.createJsonMapper(); + private SimpleCache allowedClasses = new SimpleCache<>(); + /** * Constructs a new {@code CrudServiceRestController} with the given CRUD service. + * * @param crudService the CRUD service to use */ public AbstractCrudServiceRestController(CrudService crudService) { @@ -56,8 +62,9 @@ public AbstractCrudServiceRestController(CrudService crudService) { /** * Creates or updates an entity of the specified class. + * * @param className the fully qualified class name of the entity - * @param json the JSON representation of the entity + * @param json the JSON representation of the entity * @return the persisted entity */ @RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT}) @@ -69,8 +76,9 @@ public ResponseEntity createOrSave(@PathVariable String className, @Requ /** * Deletes an entity by its class name and ID. + * * @param className the fully qualified class name of the entity - * @param id the entity ID + * @param id the entity ID * @return the deleted entity ID if successful, or 404 if not found */ @DeleteMapping("/{id}") @@ -87,8 +95,9 @@ public ResponseEntity delete(@PathVariable String className, @PathVariab /** * Retrieves an entity by its class name and ID. + * * @param className the fully qualified class name of the entity - * @param id the entity ID + * @param id the entity ID * @return the entity if found, or 404 if not found */ @GetMapping("/{id}") @@ -103,7 +112,8 @@ public ResponseEntity get(@PathVariable String className, @PathVariable /** * Finds entities by query parameters. - * @param className the fully qualified class name of the entity + * + * @param className the fully qualified class name of the entity * @param parameters the query parameters * @return the list of matching entities */ @@ -116,7 +126,8 @@ public ResponseEntity> find(@PathVariable String className, @Reques /** * Gets the ID of an entity by query parameters. - * @param className the fully qualified class name of the entity + * + * @param className the fully qualified class name of the entity * @param parameters the query parameters * @return the entity ID */ @@ -129,8 +140,9 @@ public ResponseEntity getId(@PathVariable String className, @RequestBody /** * Parses a JSON string into an entity object of the specified class. + * * @param className the fully qualified class name - * @param json the JSON string + * @param json the JSON string * @return the entity object * @throws ValidationError if parsing fails */ @@ -145,15 +157,29 @@ private Object parseJson(String className, String json) { /** * Loads a class by its fully qualified name. + * * @param className the class name * @return the {@link Class} object * @throws ValidationError if the class is not found */ private Class loadClass(String className) { - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - throw new ValidationError("Class not found " + className + " - " + e.getMessage(), e); + initAllowedClasses(); + Class entityClass = allowedClasses.get(className); + if (entityClass == null) { + throw new ValidationError("Class not allowed: " + className); + } + return entityClass; + } + + private void initAllowedClasses() { + if (allowedClasses == null || allowedClasses.isEmpty()) { + ViewDescriptorFactory viewDescriptorFactory = Containers.get().findObject(ViewDescriptorFactory.class); + if (viewDescriptorFactory != null) { + viewDescriptorFactory.findDescriptorsByType("crud").forEach(d -> { + var entityClass = d.getKey(); + allowedClasses.add(entityClass.getName(), entityClass); + }); + } } } } diff --git a/platform/core/commons/src/main/java/tools/dynamia/commons/SimpleCache.java b/platform/core/commons/src/main/java/tools/dynamia/commons/SimpleCache.java index 8686d5cf..baa27ea1 100644 --- a/platform/core/commons/src/main/java/tools/dynamia/commons/SimpleCache.java +++ b/platform/core/commons/src/main/java/tools/dynamia/commons/SimpleCache.java @@ -53,6 +53,19 @@ public void add(K key, V value) { data.put(key, value); } + /** + * Alias to add + * @param key + * @param value + */ + public void put(K key, V value) { + data.put(key, value); + } + + public void putIfAbsent(K key, V value) { + data.putIfAbsent(key, value); + } + /** * Retrieves a value from the cache by key. *