Improvements for RESTful API

Former-commit-id: fe6e680733
restapi
Jens Pelzetter 2020-06-05 20:32:27 +02:00
parent 2d15639124
commit 4a4e4beb78
15 changed files with 489 additions and 212 deletions

View File

@ -29,7 +29,7 @@ import javax.enterprise.context.Dependent;
@Dependent @Dependent
public class IdentifierParser { public class IdentifierParser {
public Identifier extractIdentifier(final String identifierParam) { public Identifier parseIdentifier(final String identifierParam) {
Objects.requireNonNull(identifierParam, "identifier param is null."); Objects.requireNonNull(identifierParam, "identifier param is null.");
if (identifierParam.startsWith(ApiConstants.IDENTIFIER_PREFIX_ID)) { if (identifierParam.startsWith(ApiConstants.IDENTIFIER_PREFIX_ID)) {

View File

@ -18,6 +18,7 @@
*/ */
package org.libreccm.api.admin.categorization; package org.libreccm.api.admin.categorization;
import org.libreccm.api.admin.categorization.dto.CategorizationData;
import org.libreccm.api.admin.categorization.dto.CategoryData; import org.libreccm.api.admin.categorization.dto.CategoryData;
import org.libreccm.api.dto.ListView; import org.libreccm.api.dto.ListView;
import org.libreccm.core.CoreConstants; import org.libreccm.core.CoreConstants;
@ -28,12 +29,14 @@ import javax.enterprise.context.RequestScoped;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.PUT; import javax.ws.rs.PUT;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
/** /**
@ -190,13 +193,56 @@ public class CategoriesApi {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@GET
@Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/subcategories")
@Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public ListView<CategoryData> getSubCategories(
@PathParam("domainIdentifier") final String domainIdentifierParam,
@PathParam("path") final String categoryPathTokens,
@QueryParam("limit") @DefaultValue("20") final int limit,
@QueryParam("offset") @DefaultValue("20") final int offset
) {
throw new UnsupportedOperationException();
}
@GET
@Path("/ID-{categoryId}/subcategories")
@Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public ListView<CategoryData> getSubCategories(
@PathParam("categoryId") final long categoryId,
@QueryParam("limit") @DefaultValue("20") final int limit,
@QueryParam("offset") @DefaultValue("20") final int offset
) {
throw new UnsupportedOperationException();
}
@GET
@Path("/UUID-{categoryId}/subcategories")
@Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public ListView<CategoryData> getSubCategories(
@PathParam("categoryUuid") final String uuid,
@QueryParam("limit") @DefaultValue("20") final int limit,
@QueryParam("offset") @DefaultValue("20") final int offset
) {
throw new UnsupportedOperationException();
}
@GET @GET
@Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects") @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData getObjectsInCategory( public ListView<CategorizationData> getObjectsInCategory(
@PathParam("domainIdentifier") final String domainIdentifierParam, @PathParam("domainIdentifier") final String domainIdentifierParam,
@PathParam("path") final String categoryPathTokens @PathParam("path") final String categoryPathTokens
) { ) {
@ -209,7 +255,7 @@ public class CategoriesApi {
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData getCategoryObjectsInCategory( public ListView<CategorizationData> getCategoryObjectsInCategory(
@PathParam("categoryId") final long categoryId @PathParam("categoryId") final long categoryId
) { ) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -221,7 +267,7 @@ public class CategoriesApi {
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData getObjectsInCategory( public ListView<CategorizationData> getObjectsInCategory(
@PathParam("categoryId") final String categoryUuid @PathParam("categoryId") final String categoryUuid
) { ) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.api.admin.categorization;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.categorization.Domain;
import org.libreccm.categorization.DomainRepository;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
* Shared repository for the classes of the categorization RESTful api.
*
* All methods in this class will throw a {@link WebApplicationException} with a
* 404 status code if the requested entity is not found.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Dependent
public class CategorizationApiRepository {
@Inject
private DomainRepository domainRepository;
@Inject
private IdentifierParser identifierParser;
public Domain findDomain(final String domainIdentifier) {
final Identifier identifier = identifierParser.parseIdentifier(
domainIdentifier
);
switch (identifier.getType()) {
case ID:
return domainRepository
.findById(Long.parseLong(identifier.getIdentifier()))
.orElseThrow(
() -> new WebApplicationException(
String.format(
"No Domain with ID %s found.",
identifier.getIdentifier()
),
Response.Status.NOT_FOUND
)
);
case UUID:
return domainRepository
.findByUuid(identifier.getIdentifier())
.orElseThrow(
() -> new WebApplicationException(
String.format(
"No Domain with UUID %s found.",
identifier.getIdentifier()
),
Response.Status.NOT_FOUND
)
);
default:
return domainRepository
.findByDomainKey(identifier.getIdentifier())
.orElseThrow(
() -> new WebApplicationException(
String.format(
"No Domain with domain key %s found.",
identifier.getIdentifier()
),
Response.Status.NOT_FOUND
)
);
}
}
}

View File

@ -21,10 +21,7 @@ package org.libreccm.api.admin.categorization.dto;
import org.libreccm.categorization.Category; import org.libreccm.categorization.Category;
import org.libreccm.l10n.LocalizedString; import org.libreccm.l10n.LocalizedString;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* *
@ -50,17 +47,12 @@ public class CategoryData {
private boolean abstractCategory; private boolean abstractCategory;
private List<CategorizationData> objects;
private List<AssociatedCategoryData> subCategories;
private AssociatedCategoryData parentCategory; private AssociatedCategoryData parentCategory;
private long categoryOrder; private long categoryOrder;
public CategoryData() { public CategoryData() {
objects = new ArrayList<>(); // Nothing
subCategories = new ArrayList<>();
} }
public CategoryData(final Category fromCategory) { public CategoryData(final Category fromCategory) {
@ -77,16 +69,6 @@ public class CategoryData {
enabled = fromCategory.isEnabled(); enabled = fromCategory.isEnabled();
visible = fromCategory.isVisible(); visible = fromCategory.isVisible();
abstractCategory = fromCategory.isAbstractCategory(); abstractCategory = fromCategory.isAbstractCategory();
objects = fromCategory
.getObjects()
.stream()
.map(CategorizationData::new)
.collect(Collectors.toList());
subCategories = fromCategory
.getSubCategories()
.stream()
.map(AssociatedCategoryData::new)
.collect(Collectors.toList());
parentCategory = new AssociatedCategoryData( parentCategory = new AssociatedCategoryData(
fromCategory.getParentCategory() fromCategory.getParentCategory()
); );
@ -164,23 +146,6 @@ public class CategoryData {
this.abstractCategory = abstractCategory; this.abstractCategory = abstractCategory;
} }
public List<CategorizationData> getObjects() {
return new ArrayList<>(objects);
}
public void setObjects(final List<CategorizationData> objects) {
this.objects = new ArrayList<>(objects);
}
public List<AssociatedCategoryData> getSubCategories() {
return new ArrayList<>(subCategories);
}
public void setSubCategories(
final List<AssociatedCategoryData> subCategories) {
this.subCategories = new ArrayList<>(subCategories);
}
public AssociatedCategoryData getParentCategory() { public AssociatedCategoryData getParentCategory() {
return parentCategory; return parentCategory;
} }

View File

@ -203,14 +203,23 @@ public class GroupsApi {
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public List<GroupUserMembership> getMembers( public ListView<GroupUserMembership> getMembers(
@PathParam("groupIdentifier") final String groupIdentifier @PathParam("groupIdentifier") final String groupIdentifier,
@QueryParam("limit") @DefaultValue("20") final int limit,
@QueryParam("offset") @DefaultValue("0") final int offset
) { ) {
return repository.findGroup(groupIdentifier) final Group group = repository.findGroup(groupIdentifier);
.getMemberships()
return new ListView<>(
groupRepository
.findGroupMemberships(group, limit, offset)
.stream() .stream()
.map(GroupUserMembership::new) .map(GroupUserMembership::new)
.collect(Collectors.toList()); .collect(Collectors.toList()),
groupRepository.countGroupMemberships(group),
limit,
offset
);
} }
@PUT @PUT

View File

@ -48,6 +48,8 @@ import java.net.URI;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
/** /**
* *
@ -56,31 +58,34 @@ import javax.ws.rs.WebApplicationException;
@RequestScoped @RequestScoped
@Path("/roles") @Path("/roles")
public class RolesApi { public class RolesApi {
@Context
private UriInfo uriInfo;
@Inject @Inject
private CcmObjectRepository ccmObjectRepository; private CcmObjectRepository ccmObjectRepository;
@Inject @Inject
private IdentifierParser identifierExtractor; private IdentifierParser identifierExtractor;
@Inject @Inject
private PartyRepository partyRepository; private PartyRepository partyRepository;
@Inject @Inject
private PermissionManager permissionManager; private PermissionManager permissionManager;
@Inject @Inject
private PermissionRepository permissionRepository; private PermissionRepository permissionRepository;
@Inject @Inject
private SecurityApiRepository repository; private SecurityApiRepository repository;
@Inject @Inject
private RoleManager roleManager; private RoleManager roleManager;
@Inject @Inject
private RoleRepository roleRepository; private RoleRepository roleRepository;
@GET @GET
@Path("/") @Path("/")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ -93,7 +98,7 @@ public class RolesApi {
) { ) {
final long count = roleRepository.countAll(); final long count = roleRepository.countAll();
final List<Role> roles = roleRepository.findAll(limit, offset); final List<Role> roles = roleRepository.findAll(limit, offset);
return new ListView<>( return new ListView<>(
roles.stream().map(RoleData::new).collect(Collectors.toList()), roles.stream().map(RoleData::new).collect(Collectors.toList()),
count, count,
@ -101,7 +106,7 @@ public class RolesApi {
offset offset
); );
} }
@GET @GET
@Path("/{roleIdentifier}") @Path("/{roleIdentifier}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ -113,7 +118,7 @@ public class RolesApi {
) { ) {
return new RoleData(repository.findRole(roleIdentifier)); return new RoleData(repository.findRole(roleIdentifier));
} }
@POST @POST
@Path("/") @Path("/")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ -124,14 +129,14 @@ public class RolesApi {
final Role role = new Role(); final Role role = new Role();
role.setName(roleData.getName()); role.setName(roleData.getName());
role.setDescription(roleData.getDescription()); role.setDescription(roleData.getDescription());
roleRepository.save(role); roleRepository.save(role);
return Response.created( return Response.created(
URI.create(String.format("/api/admin/roles/%s", role.getName())) URI.create(String.format("/api/admin/roles/%s", role.getName()))
).build(); ).build();
} }
@PUT @PUT
@Path("/{roleIdentifier}") @Path("/{roleIdentifier}")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ -143,20 +148,20 @@ public class RolesApi {
final RoleData roleData final RoleData roleData
) { ) {
final Role role = repository.findRole(roleIdentifier); final Role role = repository.findRole(roleIdentifier);
if (roleData != null if (roleData != null
&& roleData.getName() != null && roleData.getName() != null
&& !roleData.getName().equals(role.getName())) { && !roleData.getName().equals(role.getName())) {
role.setName(roleData.getName()); role.setName(roleData.getName());
} }
roleRepository.save(role); roleRepository.save(role);
return Response return Response
.ok(String.format("Role %s updated succesfully.", roleIdentifier)) .ok(String.format("Role %s updated succesfully.", roleIdentifier))
.build(); .build();
} }
@DELETE @DELETE
@Path("/{roleIdentifier}") @Path("/{roleIdentifier}")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ -172,23 +177,32 @@ public class RolesApi {
.ok(String.format("Role %s deleted successfully.", roleIdentifier)) .ok(String.format("Role %s deleted successfully.", roleIdentifier))
.build(); .build();
} }
@GET @GET
@Path("/{roleIdentifier}/members") @Path("/{roleIdentifier}/members")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public List<RolePartyMembership> getMembers( public ListView<RolePartyMembership> getMembers(
@PathParam("roleIdentifier") final String roleIdentifier @PathParam("roleIdentifier") final String roleIdentifier,
@QueryParam("limit") @DefaultValue("20") final int limit,
@QueryParam("offset") @DefaultValue("0") final int offset
) { ) {
return repository.findRole(roleIdentifier) final Role role = repository.findRole(roleIdentifier);
.getMemberships() final long count = roleRepository.countMembershipsByRole(role);
.stream() return new ListView<>(
.map(RolePartyMembership::new) roleRepository
.collect(Collectors.toList()); .findMembershipsByRole(role, limit, offset)
.stream()
.map(RolePartyMembership::new)
.collect(Collectors.toList()),
count,
limit,
offset
);
} }
@PUT @PUT
@Path("/{roleIdentifier}/members/{partyIdentifier}") @Path("/{roleIdentifier}/members/{partyIdentifier}")
@AuthorizationRequired @AuthorizationRequired
@ -200,9 +214,9 @@ public class RolesApi {
) { ) {
final Role role = repository.findRole(groupIdentifier); final Role role = repository.findRole(groupIdentifier);
final Party party = repository.findParty(partyIdentifier); final Party party = repository.findParty(partyIdentifier);
roleManager.assignRoleToParty(role, party); roleManager.assignRoleToParty(role, party);
return Response return Response
.ok( .ok(
String.format( String.format(
@ -212,7 +226,7 @@ public class RolesApi {
) )
).build(); ).build();
} }
@DELETE @DELETE
@Path("/{roleIdentifier}/members/{partyIdentifier}") @Path("/{roleIdentifier}/members/{partyIdentifier}")
@AuthorizationRequired @AuthorizationRequired
@ -224,9 +238,9 @@ public class RolesApi {
) { ) {
final Role role = repository.findRole(groupIdentifier); final Role role = repository.findRole(groupIdentifier);
final Party party = repository.findParty(partyIdentifier); final Party party = repository.findParty(partyIdentifier);
roleManager.removeRoleFromParty(role, party); roleManager.removeRoleFromParty(role, party);
return Response return Response
.ok( .ok(
String.format( String.format(
@ -237,23 +251,31 @@ public class RolesApi {
) )
.build(); .build();
} }
@GET @GET
@Path("/{roleIdentifier}/permissions") @Path("/{roleIdentifier}/permissions")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public List<RolePermission> getPermissions( public ListView<RolePermission> getPermissions(
@PathParam("roleIdentifier") final String roleIdentifier @PathParam("roleIdentifier") final String roleIdentifier,
@QueryParam("limit") @DefaultValue("20") final int limit,
@QueryParam("offset") @DefaultValue("0") final int offset
) { ) {
return repository.findRole(roleIdentifier) final Role role = repository.findRole(roleIdentifier);
.getPermissions() return new ListView<>(
.stream() permissionRepository
.map(RolePermission::new) .findPermissionsForRole(role, limit, offset)
.collect(Collectors.toList()); .stream()
.map(RolePermission::new)
.collect(Collectors.toList()),
permissionRepository.countPermissionsForRole(role),
limit,
offset
);
} }
@POST @POST
@Path("/{roleIdentifier}/permissions") @Path("/{roleIdentifier}/permissions")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ -266,7 +288,7 @@ public class RolesApi {
) { ) {
final Role role = repository.findRole(roleIdentifier); final Role role = repository.findRole(roleIdentifier);
final String privilege = permissionData.getGrantedPrivilege(); final String privilege = permissionData.getGrantedPrivilege();
final Permission permission; final Permission permission;
if (permissionData.getObject() != null) { if (permissionData.getObject() != null) {
final CcmObject object = ccmObjectRepository final CcmObject object = ccmObjectRepository
@ -317,7 +339,7 @@ public class RolesApi {
).build(); ).build();
} else { } else {
permission = permissionManager.grantPrivilege(privilege, role); permission = permissionManager.grantPrivilege(privilege, role);
return Response.created( return Response.created(
URI.create( URI.create(
String.format( String.format(
@ -330,7 +352,7 @@ public class RolesApi {
} }
} }
} }
@DELETE @DELETE
@Path("/{roleIdentifier}/permissions/{permissionIdentifier}") @Path("/{roleIdentifier}/permissions/{permissionIdentifier}")
@AuthorizationRequired @AuthorizationRequired
@ -343,10 +365,10 @@ public class RolesApi {
final String permissionIdentifierParam final String permissionIdentifierParam
) { ) {
final Role role = repository.findRole(roleIdentifier); final Role role = repository.findRole(roleIdentifier);
final Identifier permissionIdentifier = identifierExtractor final Identifier permissionIdentifier = identifierExtractor
.extractIdentifier(roleIdentifier); .parseIdentifier(roleIdentifier);
final Permission permission; final Permission permission;
switch (permissionIdentifier.getType()) { switch (permissionIdentifier.getType()) {
case ID: case ID:
@ -375,18 +397,18 @@ public class RolesApi {
Response.Status.NOT_FOUND Response.Status.NOT_FOUND
) )
); );
break; break;
default: default:
return Response return Response
.status(Response.Status.BAD_REQUEST) .status(Response.Status.BAD_REQUEST)
.entity("Permissions can only be identified by ID or UUID.") .entity("Permissions can only be identified by ID or UUID.")
.build(); .build();
} }
permissionRepository.delete(permission); permissionRepository.delete(permission);
return Response.ok().build(); return Response.ok().build();
} }
} }

View File

@ -63,7 +63,7 @@ class SecurityApiRepository {
protected Group findGroup(final String groupIdentifier) { protected Group findGroup(final String groupIdentifier) {
final Identifier identifier = identifierExtractor final Identifier identifier = identifierExtractor
.extractIdentifier(groupIdentifier); .parseIdentifier(groupIdentifier);
switch (identifier.getType()) { switch (identifier.getType()) {
case ID: case ID:
@ -107,7 +107,7 @@ class SecurityApiRepository {
protected Party findParty(final String partyIdentifier) { protected Party findParty(final String partyIdentifier) {
final Identifier identifier = identifierExtractor final Identifier identifier = identifierExtractor
.extractIdentifier(partyIdentifier); .parseIdentifier(partyIdentifier);
switch (identifier.getType()) { switch (identifier.getType()) {
case ID: case ID:
@ -152,7 +152,7 @@ class SecurityApiRepository {
protected Role findRole(final String roleIdentifier) { protected Role findRole(final String roleIdentifier) {
final Identifier identifier = identifierExtractor final Identifier identifier = identifierExtractor
.extractIdentifier(roleIdentifier); .parseIdentifier(roleIdentifier);
switch (identifier.getType()) { switch (identifier.getType()) {
case ID: case ID:
@ -196,7 +196,7 @@ class SecurityApiRepository {
protected User findUser(final String identifierParam) { protected User findUser(final String identifierParam) {
final Identifier identifier = identifierExtractor final Identifier identifier = identifierExtractor
.extractIdentifier(identifierParam); .parseIdentifier(identifierParam);
switch (identifier.getType()) { switch (identifier.getType()) {
case ID: case ID:

View File

@ -41,19 +41,19 @@ public class RoleData {
private LocalizedString description; private LocalizedString description;
private List<RolePartyMembership> memberships; // private List<RolePartyMembership> memberships;
private List<RolePermission> permissions; private List<RolePermission> permissions;
private List<RoleAssignedTask> assignedTasks; // private List<RoleAssignedTask> assignedTasks;
/** /**
* Parameterless constructor for creating empty instances. * Parameterless constructor for creating empty instances.
*/ */
public RoleData() { public RoleData() {
memberships = new ArrayList<>(); // membership = new ArrayList<>();
permissions = new ArrayList<>(); permissions = new ArrayList<>();
assignedTasks = new ArrayList<>(); // assignedTasks = new ArrayList<>();
} }
public RoleData(final Role role) { public RoleData(final Role role) {
@ -64,23 +64,23 @@ public class RoleData {
name = role.getName(); name = role.getName();
description = role.getDescription(); description = role.getDescription();
memberships = role // memberships = role
.getMemberships() // .getMemberships()
.stream() // .stream()
.map(RolePartyMembership::new) // .map(RolePartyMembership::new)
.collect(Collectors.toList()); // .collect(Collectors.toList());
//
permissions = role // permissions = role
.getPermissions() // .getPermissions()
.stream() // .stream()
.map(RolePermission::new) // .map(RolePermission::new)
.collect(Collectors.toList()); // .collect(Collectors.toList());
//
assignedTasks = role // assignedTasks = role
.getAssignedTasks() // .getAssignedTasks()
.stream() // .stream()
.map(RoleAssignedTask::new) // .map(RoleAssignedTask::new)
.collect(Collectors.toList()); // .collect(Collectors.toList());
} }
public long getRoleId() { public long getRoleId() {
@ -107,21 +107,21 @@ public class RoleData {
this.name = name; this.name = name;
} }
public List<RolePartyMembership> getMemberships() { // public List<RolePartyMembership> getMemberships() {
return new ArrayList<>(memberships); // return new ArrayList<>(memberships);
} // }
//
public void setMemberships(final List<RolePartyMembership> memberships) { // public void setMemberships(final List<RolePartyMembership> memberships) {
this.memberships = new ArrayList<>(memberships); // this.memberships = new ArrayList<>(memberships);
} // }
//
public List<RoleAssignedTask> getAssignedTasks() { // public List<RoleAssignedTask> getAssignedTasks() {
return new ArrayList<>(assignedTasks); // return new ArrayList<>(assignedTasks);
} // }
//
public void setAssignedTasks(final List<RoleAssignedTask> assignedTasks) { // public void setAssignedTasks(final List<RoleAssignedTask> assignedTasks) {
this.assignedTasks = new ArrayList<>(assignedTasks); // this.assignedTasks = new ArrayList<>(assignedTasks);
} // }
public LocalizedString getDescription() { public LocalizedString getDescription() {
return description; return description;

View File

@ -66,31 +66,38 @@ import javax.persistence.Table;
@NamedQuery( @NamedQuery(
name = "Group.findByName", name = "Group.findByName",
query = "SELECT g FROM Group g WHERE g.name = :name " query = "SELECT g FROM Group g WHERE g.name = :name "
+ "ORDER BY g.name") + "ORDER BY g.name"),
,
@NamedQuery( @NamedQuery(
name = "Group.searchByName", name = "Group.searchByName",
query = "SELECT g FROM Group g " query = "SELECT g FROM Group g "
+ "WHERE LOWER(g.name) LIKE CONCAT(LOWER(:name), '%') " + "WHERE LOWER(g.name) LIKE CONCAT(LOWER(:name), '%') "
+ "ORDER BY g.name") + "ORDER BY g.name"),
,
@NamedQuery( @NamedQuery(
name = "Group.findAllOrderedByGroupName", name = "Group.findAllOrderedByGroupName",
query = "SELECT g FROM Group g ORDER BY g.name") query = "SELECT g FROM Group g ORDER BY g.name"),
,
@NamedQuery( @NamedQuery(
name = "Group.findByMember", name = "Group.findByMember",
query = "SELECT g FROM Group g " query = "SELECT g FROM Group g "
+ "JOIN g.memberships m " + "JOIN g.memberships m "
+ "WHERE m.member = :member" + "WHERE m.member = :member"
),
@NamedQuery(
name = "Group.findMemberships",
query = "SELECT m FROM GroupMembership m "
+ "JOIN m.member u "
+ "WHERE m.group = :group "
+ "ORDER BY u.name"
),
@NamedQuery(
name = "Group.countMemberships",
query = "SELECT COUNT(m) FROM GroupMembership m WHERE m.group = :group"
) )
}) })
@NamedEntityGraphs({ @NamedEntityGraphs({
@NamedEntityGraph( @NamedEntityGraph(
name = "Group.withMembersAndRoleMemberships", name = "Group.withMembersAndRoleMemberships",
attributeNodes = { attributeNodes = {
@NamedAttributeNode(value = "memberships") @NamedAttributeNode(value = "memberships"),
,
@NamedAttributeNode(value = "roleMemberships", @NamedAttributeNode(value = "roleMemberships",
subgraph = "role")}, subgraph = "role")},
subgraphs = { subgraphs = {
@ -99,8 +106,7 @@ import javax.persistence.Table;
attributeNodes = { attributeNodes = {
@NamedAttributeNode(value = "role", @NamedAttributeNode(value = "role",
subgraph = "permissions") subgraph = "permissions")
}) }),
,
@NamedSubgraph( @NamedSubgraph(
name = "permissions", name = "permissions",
attributeNodes = { attributeNodes = {
@ -177,4 +183,4 @@ public class Group extends Party implements Serializable, Exportable {
return super.hashCode(); return super.hashCode();
} }
} }

View File

@ -26,6 +26,7 @@ import javax.persistence.TypedQuery;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -62,11 +63,11 @@ public class GroupRepository extends AbstractEntityRepository<Long, Group> {
return entity.getPartyId() == 0; return entity.getPartyId() == 0;
} }
public Optional<Group> findByUuid(final String uuid) { public Optional<Group> findByUuid(final String uuid) {
final TypedQuery<Group> query = getEntityManager() final TypedQuery<Group> query = getEntityManager()
.createNamedQuery("Group.findByUuid", Group.class); .createNamedQuery("Group.findByUuid", Group.class);
query.setParameter("uuid", uuid); query.setParameter("uuid", uuid);
final List<Group> result = query.getResultList(); final List<Group> result = query.getResultList();
if (result.isEmpty()) { if (result.isEmpty()) {
@ -107,6 +108,38 @@ public class GroupRepository extends AbstractEntityRepository<Long, Group> {
return query.getResultList(); return query.getResultList();
} }
public List<GroupMembership> findGroupMemberships(
final Group group,
final int limit,
final int offset
) {
return getEntityManager()
.createNamedQuery("Group.findMemberships", GroupMembership.class)
.setParameter(
"group",
Objects.requireNonNull(
group,
"Can't retrieve menberships for group null."
)
)
.setMaxResults(limit)
.setFirstResult(offset)
.getResultList();
}
public long countGroupMemberships(final Group group) {
return getEntityManager()
.createNamedQuery("Group.countMemberships", Long.class)
.setParameter(
"group",
Objects.requireNonNull(
group,
"Can't count menberships for group null."
)
)
.getSingleResult();
}
/** /**
* Tries to find a group which name contains a provided token. * Tries to find a group which name contains a provided token.
* *
@ -134,10 +167,10 @@ public class GroupRepository extends AbstractEntityRepository<Long, Group> {
public void save(final Group group) { public void save(final Group group) {
super.save(group); super.save(group);
} }
@Override @Override
public void initNewEntity(final Group group) { public void initNewEntity(final Group group) {
group.setUuid(UUID.randomUUID().toString()); group.setUuid(UUID.randomUUID().toString());
} }

View File

@ -70,44 +70,65 @@ import javax.persistence.TemporalType;
@Entity @Entity
@Table(name = "PERMISSIONS", schema = DB_SCHEMA) @Table(name = "PERMISSIONS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "Permission.findByUuid", @NamedQuery(
query = "SELECT p FROM Permission p WHERE p.uuid = :uuid"), name = "Permission.findByUuid",
@NamedQuery(name = "Permission.findByCustomPermId", query = "SELECT p FROM Permission p WHERE p.uuid = :uuid"
query = "SELECT p FROM Permission p " ),
+ "WHERE p.grantedPrivilege = :privilege " @NamedQuery(
+ "AND p.grantee = :grantee " name = "Permission.findByCustomPermId",
+ "AND p.object = :object"), query = "SELECT p FROM Permission p "
@NamedQuery(name = "Permission.existsForPrivilegeRoleObject", + "WHERE p.grantedPrivilege = :privilege "
query = "SELECT COUNT(p) FROM Permission p " + "AND p.grantee = :grantee "
+ "WHERE p.grantedPrivilege = :privilege " + "AND p.object = :object"
+ "AND p.grantee = :grantee " ),
+ "AND p.object = :object"), @NamedQuery(
@NamedQuery(name = "Permission.existsDirectForPrivilegeRoleObject", name = "Permission.existsForPrivilegeRoleObject",
query = "SELECT COUNT(p) FROM Permission p " query = "SELECT COUNT(p) FROM Permission p "
+ "WHERE p.grantedPrivilege = :privilege " + "WHERE p.grantedPrivilege = :privilege "
+ "AND p.grantee = :grantee " + "AND p.grantee = :grantee "
+ "AND p.object = :object " + "AND p.object = :object"
+ "AND p.inherited = false"), ),
@NamedQuery(name = "Permission.existsInheritedForPrivilegeRoleObject", @NamedQuery(
query = "SELECT COUNT(p) FROM Permission p " name = "Permission.existsDirectForPrivilegeRoleObject",
+ "WHERE p.grantedPrivilege = :privilege " query = "SELECT COUNT(p) FROM Permission p "
+ "AND p.grantee = :grantee " + "WHERE p.grantedPrivilege = :privilege "
+ "AND p.object = :object " + "AND p.grantee = :grantee "
+ "AND p.inherited = true"), + "AND p.object = :object "
@NamedQuery(name = "Permission.existsForPrivilegeAndRole", + "AND p.inherited = false"
query = "SELECT COUNT(p) FROM Permission p " ),
+ "WHERE p.grantedPrivilege = :privilege " @NamedQuery(
+ "AND p.grantee = :grantee " name = "Permission.existsInheritedForPrivilegeRoleObject",
+ "AND p.object IS NULL"), query = "SELECT COUNT(p) FROM Permission p "
@NamedQuery(name = "Permission.findPermissionsForRole", + "WHERE p.grantedPrivilege = :privilege "
query = "SELECT p FROM Permission p " + "AND p.grantee = :grantee "
+ "WHERE p.grantee = :grantee"), + "AND p.object = :object "
@NamedQuery(name = "Permission.findPermissionsForCcmObject", + "AND p.inherited = true"
query = "SELECT p FROM Permission p " ),
+ "WHERE p.object = :object"), @NamedQuery(
@NamedQuery(name = "Permission.findPermissionsForRoleAndObject", name = "Permission.existsForPrivilegeAndRole",
query = "SELECT p FROM Permission p " query = "SELECT COUNT(p) FROM Permission p "
+ "WHERE p.object = :object and p.grantee = :grantee") + "WHERE p.grantedPrivilege = :privilege "
+ "AND p.grantee = :grantee "
+ "AND p.object IS NULL"
),
@NamedQuery(
name = "Permission.findPermissionsForRole",
query = "SELECT p FROM Permission p WHERE p.grantee = :grantee"
),
@NamedQuery(
name = "Permission.countPermissionsForRole",
query = "SELECT COUNT(p) FROM Permission p WHERE p.grantee = :grantee"
),
@NamedQuery(
name = "Permission.findPermissionsForCcmObject",
query = "SELECT p FROM Permission p "
+ "WHERE p.object = :object"
),
@NamedQuery(
name = "Permission.findPermissionsForRoleAndObject",
query = "SELECT p FROM Permission p "
+ "WHERE p.object = :object and p.grantee = :grantee"
)
}) })
@XmlRootElement(name = "permission", namespace = CORE_XML_NS) @XmlRootElement(name = "permission", namespace = CORE_XML_NS)
@ -408,7 +429,7 @@ public class Permission implements Serializable, Exportable {
public JsonObject toJson() { public JsonObject toJson() {
return buildJson().build(); return buildJson().build();
} }
@Override @Override
public String toString() { public String toString() {
return String.format("%s{ " return String.format("%s{ "

View File

@ -21,6 +21,9 @@ package org.libreccm.security;
import org.libreccm.core.AbstractEntityRepository; import org.libreccm.core.AbstractEntityRepository;
import org.libreccm.core.CcmObject; import org.libreccm.core.CcmObject;
import java.util.List;
import java.util.Objects;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
@ -28,6 +31,8 @@ import javax.persistence.TypedQuery;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import javax.transaction.Transactional;
/** /**
* A repository class for {@link Permission}. * A repository class for {@link Permission}.
* *
@ -55,11 +60,13 @@ public class PermissionRepository
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED)
public String getIdAttributeName() { public String getIdAttributeName() {
return "permissionId"; return "permissionId";
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED)
public Long getIdOfEntity(final Permission entity) { public Long getIdOfEntity(final Permission entity) {
return entity.getPermissionId(); return entity.getPermissionId();
} }
@ -73,6 +80,7 @@ public class PermissionRepository
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED)
public void initNewEntity(final Permission permission) { public void initNewEntity(final Permission permission) {
permission.setUuid(UUID.randomUUID().toString()); permission.setUuid(UUID.randomUUID().toString());
@ -97,6 +105,7 @@ public class PermissionRepository
* *
* @return An optional either with the found item or empty * @return An optional either with the found item or empty
*/ */
@Transactional(Transactional.TxType.REQUIRED)
public Optional<Permission> findByCustomPermId(final String privilege, public Optional<Permission> findByCustomPermId(final String privilege,
final Role grantee, final Role grantee,
final Object object) { final Object object) {
@ -116,6 +125,38 @@ public class PermissionRepository
} }
} }
@Transactional(Transactional.TxType.REQUIRED)
public List<Permission> findPermissionsForRole(
final Role role, final int limit, final int offset
) {
return getEntityManager()
.createNamedQuery(
"Permission.findPermissionsForRole", Permission.class)
.setParameter(
"grantee",
Objects.requireNonNull(
role,
"Can't retrieve permissions for role null"
)
)
.setMaxResults(limit)
.setFirstResult(offset)
.getResultList();
}
@Transactional(Transactional.TxType.REQUIRED)
public long countPermissionsForRole(final Role role) {
return getEntityManager()
.createNamedQuery("Permission.countPermissionsForRole", Long.class)
.setParameter(
"grantee",
Objects.requireNonNull(
role,
"Can't count permissions for role null."
)
)
.getSingleResult();
}
/** /**
* Checks if a not inherited permission granting the provided * Checks if a not inherited permission granting the provided
* {@code privilege} on the provided {@code object} to the provided * {@code privilege} on the provided {@code object} to the provided
@ -128,6 +169,8 @@ public class PermissionRepository
* @return {@code true} if there is a matching permission, {@code false} if * @return {@code true} if there is a matching permission, {@code false} if
* not. * not.
*/ */
@Transactional(Transactional.TxType.REQUIRED)
public boolean existsPermission(final String privilege, public boolean existsPermission(final String privilege,
final Role grantee, final Role grantee,
final CcmObject object) { final CcmObject object) {
@ -141,10 +184,11 @@ public class PermissionRepository
return query.getSingleResult() > 0; return query.getSingleResult() > 0;
} }
public boolean existsInheritedPermission(final String privilege, @Transactional(Transactional.TxType.REQUIRED)
final Role grantee, public boolean existsInheritedPermission(final String privilege,
final CcmObject object) { final Role grantee,
final CcmObject object) {
final TypedQuery<Long> query = getEntityManager().createNamedQuery( final TypedQuery<Long> query = getEntityManager().createNamedQuery(
"Permission.existsInheritedForPrivilegeRoleObject", Long.class); "Permission.existsInheritedForPrivilegeRoleObject", Long.class);
query.setParameter(QUERY_PARAM_PRIVILEGE, privilege); query.setParameter(QUERY_PARAM_PRIVILEGE, privilege);
@ -164,8 +208,9 @@ public class PermissionRepository
* @return {@code true} if there is a matching permission, {@code false} if * @return {@code true} if there is a matching permission, {@code false} if
* not. * not.
*/ */
@Transactional(Transactional.TxType.REQUIRED)
public boolean existsPermission(final String privilege, public boolean existsPermission(final String privilege,
final Role grantee) { final Role grantee) {
final TypedQuery<Long> query = getEntityManager().createNamedQuery( final TypedQuery<Long> query = getEntityManager().createNamedQuery(
"Permission.existsForPrivilegeAndRole", Long.class); "Permission.existsForPrivilegeAndRole", Long.class);
query.setParameter(QUERY_PARAM_PRIVILEGE, privilege); query.setParameter(QUERY_PARAM_PRIVILEGE, privilege);

View File

@ -57,11 +57,31 @@ import javax.persistence.Table;
@Entity @Entity
@Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA) @Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "RoleMembership.findByUuid", @NamedQuery(
query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid"), name = "RoleMembership.findByUuid",
@NamedQuery(name = "RoleMembership.findByRoleAndMember", query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid"
query = "SELECT m FROM RoleMembership m " ),
+ "WHERE m.member = :member AND m.role = :role") @NamedQuery(
name = "RoleMembership.findByRoleAndMember",
query = "SELECT m FROM RoleMembership m "
+ "WHERE m.member = :member AND m.role = :role"
),
@NamedQuery(
name = "RoleMembership.countByParty",
query = "SELECT COUNT(m) FROM RoleMembership m WHERE m.member = :party"
),
@NamedQuery(
name = "RoleMembership.countByRole",
query = "SELECT COUNT(m) FROM RoleMembership m WHERE m.role = :role"
),
@NamedQuery(
name = "RoleMembership.findByParty",
query = "SELECT m FROM RoleMembership m WHERE m.member = :party"
),
@NamedQuery(
name = "RoleMembership.findByRole",
query = "SELECT m FROM RoleMembership m WHERE m.role = :role"
)
}) })
@XmlRootElement(name = "role-membership", namespace = CORE_XML_NS) @XmlRootElement(name = "role-membership", namespace = CORE_XML_NS)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
@ -161,7 +181,7 @@ public class RoleMembership implements Serializable, Exportable {
public boolean canEqual(final Object obj) { public boolean canEqual(final Object obj) {
return obj instanceof RoleMembership; return obj instanceof RoleMembership;
} }
public JsonObject toJson() { public JsonObject toJson() {
return buildJson().build(); return buildJson().build();
} }

View File

@ -55,7 +55,7 @@ public class RoleRepository extends AbstractEntityRepository<Long, Role> {
public Long getIdOfEntity(final Role entity) { public Long getIdOfEntity(final Role entity) {
return entity.getRoleId(); return entity.getRoleId();
} }
@Override @Override
public boolean isNew(final Role entity) { public boolean isNew(final Role entity) {
if (entity == null) { if (entity == null) {
@ -63,12 +63,12 @@ public class RoleRepository extends AbstractEntityRepository<Long, Role> {
} }
return entity.getRoleId() == 0; return entity.getRoleId() == 0;
} }
@Override @Override
public void initNewEntity(final Role role) { public void initNewEntity(final Role role) {
role.setUuid(UUID.randomUUID().toString()); role.setUuid(UUID.randomUUID().toString());
} }
public long count() { public long count() {
@ -76,13 +76,13 @@ public class RoleRepository extends AbstractEntityRepository<Long, Role> {
"Role.count", Long.class); "Role.count", Long.class);
return query.getSingleResult(); return query.getSingleResult();
} }
public Optional<Role> findByUuid(final String uuid) { public Optional<Role> findByUuid(final String uuid) {
final TypedQuery<Role> query = getEntityManager() final TypedQuery<Role> query = getEntityManager()
.createNamedQuery("Role.findByUuid", Role.class); .createNamedQuery("Role.findByUuid", Role.class);
query.setParameter("uuid", uuid); query.setParameter("uuid", uuid);
return getSingleResult(query); return getSingleResult(query);
} }
@ -150,6 +150,24 @@ public class RoleRepository extends AbstractEntityRepository<Long, Role> {
return query.getResultList(); return query.getResultList();
} }
public long countMembershipsByRole(final Role role) {
return getEntityManager()
.createNamedQuery("RoleMembership.countByRole", Long.class)
.setParameter("role", role)
.getSingleResult();
}
public List<RoleMembership> findMembershipsByRole(
final Role role, final int limit, final int offset
) {
return getEntityManager()
.createNamedQuery("RoleMembership.findByRole", RoleMembership.class)
.setParameter("role", role)
.setMaxResults(limit)
.setFirstResult(offset)
.getResultList();
}
public List<Role> searchByName(final String name) { public List<Role> searchByName(final String name) {
final TypedQuery<Role> query = getEntityManager().createNamedQuery( final TypedQuery<Role> query = getEntityManager().createNamedQuery(
"Role.searchByName", Role.class); "Role.searchByName", Role.class);

View File

@ -116,7 +116,7 @@ import javax.xml.bind.annotation.XmlTransient;
@NamedQuery(name = "User.findByGroup", @NamedQuery(name = "User.findByGroup",
query = "SELECT u FROM User u " query = "SELECT u FROM User u "
+ "JOIN u.groupMemberships m " + "JOIN u.groupMemberships m "
+ "WHERE m.group = :group") + "WHERE m.group = :group"),
}) })
@NamedEntityGraphs({ @NamedEntityGraphs({
@NamedEntityGraph( @NamedEntityGraph(