/* * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. */ package net.curisit.securis.services; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import net.curisit.integrity.commons.Utils; import net.curisit.securis.DefaultExceptionHandler; import net.curisit.securis.SeCurisException; import net.curisit.securis.db.Organization; import net.curisit.securis.db.User; import net.curisit.securis.db.User.Rol; import net.curisit.securis.ioc.EnsureTransaction; import net.curisit.securis.security.BasicSecurityContext; import net.curisit.securis.security.Securable; import net.curisit.securis.utils.TokenHelper; /** * OrganizationResource *
* CRUD and listing of organizations. Non-admin users are scoped by their * accessible organization ids when listing. * * Last reviewed by JRA on Oct 5, 2025. */ @Path("/organization") @RequestScoped public class OrganizationResource { private static final Logger LOG = LogManager.getLogger(OrganizationResource.class); @Context EntityManager em; @Context BasicSecurityContext bsc; public OrganizationResource() { } /** * index *
* List organizations. For admins returns all; for non-admins filters
* by the ids in {@link BasicSecurityContext#getOrganizationsIds()}.
*
* @return 200 OK with the list.
*/
@GET
@Path("/")
@Produces({ MediaType.APPLICATION_JSON })
@Securable
public Response index() {
LOG.info("Getting organizations list ");
em.clear();
TypedQuery
* Fetch an organization by id.
*
* @param orgid organization id (string form).
* @param token header token (unused).
* @return 200 OK with entity or 404 if not found.
*/
@GET
@Path("/{orgid}")
@Produces({ MediaType.APPLICATION_JSON })
@Securable
public Response get(@PathParam("orgid") String orgid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
LOG.info("Getting organization data for id: {}: ", orgid);
if (orgid == null || "".equals(orgid)) {
LOG.error("Organization ID is mandatory");
return Response.status(Status.NOT_FOUND).build();
}
em.clear();
Organization org = em.find(Organization.class, Integer.parseInt(orgid));
if (org == null) {
LOG.error("Organization with id {} not found in DB", orgid);
return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Organization not found, id: " + orgid).build();
}
return Response.ok(org).build();
}
/**
* create
*
* Create a new organization, setting optional parent and user members.
* Requires ADMIN.
*
* @param org payload with code/name/etc., optional parentOrgId and usersIds.
* @return 200 OK with created organization or 404 when parent/user not found.
*/
@POST
@Path("/")
@Consumes(MediaType.APPLICATION_JSON)
@Produces({ MediaType.APPLICATION_JSON })
@EnsureTransaction
@Securable(roles = Rol.ADMIN)
@RolesAllowed(BasicSecurityContext.ROL_ADMIN)
public Response create(Organization org) {
LOG.info("Creating new organization");
try {
this.setParentOrg(org, org.getParentOrgId(), em);
} catch (SeCurisException e) {
return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
}
Set
* Update an organization. Validates no cyclic parent relationship,
* updates parent and user set. Requires ADMIN.
*
* @param org new values (including optional parent/usersIds).
* @param orgid target id.
* @param token (unused) header token.
* @return 200 OK with updated organization, or specific error status.
*/
@PUT
@POST
@Path("/{orgid}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces({ MediaType.APPLICATION_JSON })
@EnsureTransaction
@Securable(roles = Rol.ADMIN)
@RolesAllowed(BasicSecurityContext.ROL_ADMIN)
public Response modify(Organization org, @PathParam("orgid") String orgid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
LOG.info("Modifying organization with id: {}", orgid);
Organization currentOrg = em.find(Organization.class, Integer.parseInt(orgid));
if (currentOrg == null) {
LOG.error("Organization with id {} not found in DB", orgid);
return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Organization not found with ID: " + orgid).build();
}
try {
this.setParentOrg(currentOrg, org.getParentOrgId(), em);
} catch (SeCurisException e) {
return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
}
if (org.getParentOrganization() != null && (isCyclicalRelationship(currentOrg.getId(), org.getParentOrganization()))) {
LOG.error("Organization parent generate a cyclical relationship, parent id {}, current id: {}", org.getParentOrgId(), currentOrg.getId());
return Response.status(Status.FORBIDDEN).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER,
"Cyclical relationships are not allowed, please change the parent organization, current Parent: " + org.getParentOrganization().getName()).build();
}
try {
setOrgUsers(currentOrg, org.getUsersIds(), em);
} catch (SeCurisException e) {
return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
}
currentOrg.setCode(org.getCode());
currentOrg.setName(org.getName());
currentOrg.setDescription(org.getDescription());
em.persist(currentOrg);
return Response.ok(currentOrg).build();
}
/**
* delete
*
* Delete an organization if it has no children. Requires ADMIN.
*
* @param orgid target id.
* @param req request (unused).
* @return 200 OK with success map, or 404/403 on constraints.
*/
@DELETE
@Path("/{orgid}")
@EnsureTransaction
@Produces({ MediaType.APPLICATION_JSON })
@Securable(roles = Rol.ADMIN)
@RolesAllowed(BasicSecurityContext.ROL_ADMIN)
public Response delete(@PathParam("orgid") String orgid, @Context HttpServletRequest req) {
LOG.info("Deleting organization with id: {}", orgid);
Organization org = em.find(Organization.class, Integer.parseInt(orgid));
if (org == null) {
LOG.error("Organization with id {} can not be deleted, It was not found in DB", orgid);
return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Organization was not found, ID: " + orgid).build();
}
if (org.getChildOrganizations() != null && !org.getChildOrganizations().isEmpty()) {
LOG.error("Organization has children and can not be deleted, ID: " + orgid);
return Response.status(Status.FORBIDDEN).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Organization has children and can not be deleted, ID: " + orgid).build();
}
em.remove(org);
return Response.ok(Utils.createMap("success", true, "id", orgid)).build();
}
// ---------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------
/**
* isCyclicalRelationship
* Detects cycles by walking up the parent chain.
*
* @param currentId
* @param parent
* @return isCyclicalRelationship
*/
private boolean isCyclicalRelationship(int currentId, Organization parent) {
while (parent != null) {
if (parent.getId() == currentId) return true;
parent = parent.getParentOrganization();
}
return false;
}
/**
* setParentOrg
* Resolve and set parent organization (nullable).
*
* @param org
* @param parentOrgId
* @param entitymanager
* @throws SeCurisException
*/
private void setParentOrg(Organization org, Integer parentOrgId, EntityManager em) throws SeCurisException {
Organization parentOrg = null;
if (parentOrgId != null) {
parentOrg = em.find(Organization.class, parentOrgId);
if (parentOrg == null) {
LOG.error("Organization parent with id {} not found in DB", org.getParentOrgId());
throw new SecurityException("Organization's parent not found with ID: " + org.getParentOrgId());
}
}
org.setParentOrganization(parentOrg);
}
/**
* setOrgUsers
* Replace organization users from the provided usernames set.
*
* @param org
* @param usersIds
* @param entityManager
* @throws SeCurisException
*/
private void setOrgUsers(Organization org, Set