PermissionsService.java
package de.dlr.shepard.auth.permission.services;
import de.dlr.shepard.auth.permission.daos.PermissionsDAO;
import de.dlr.shepard.auth.permission.io.PermissionsIO;
import de.dlr.shepard.auth.permission.model.Permissions;
import de.dlr.shepard.auth.permission.model.Roles;
import de.dlr.shepard.auth.security.PermissionLastSeenCache;
import de.dlr.shepard.auth.users.entities.User;
import de.dlr.shepard.auth.users.entities.UserGroup;
import de.dlr.shepard.auth.users.services.UserGroupService;
import de.dlr.shepard.auth.users.services.UserService;
import de.dlr.shepard.common.exceptions.InvalidAuthException;
import de.dlr.shepard.common.exceptions.InvalidRequestException;
import de.dlr.shepard.common.neo4j.entities.BasicEntity;
import de.dlr.shepard.common.util.AccessType;
import de.dlr.shepard.common.util.Constants;
import de.dlr.shepard.common.util.PermissionType;
import io.quarkus.logging.Log;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.PathSegment;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
@RequestScoped
public class PermissionsService {
@Inject
PermissionsDAO permissionsDAO;
@Inject
UserService userService;
@Inject
UserGroupService userGroupService;
@Inject
PermissionLastSeenCache permissionLastSeenCache;
/**
* @param entity the entity the permissions belong to
* @param user the user creating the permissions
* @param permissionType the initial permission type
* @return the newly created permissions
*/
public Permissions createPermissions(BasicEntity entity, User user, PermissionType permissionType) {
return permissionsDAO.createOrUpdate(new Permissions(entity, user, PermissionType.Private));
}
/**
* Searches for permissions in Neo4j.
*
* This function does NOT perform a check if the user is allowed to query the permissions of an entity.
*
* @param entityId identifies the entity that the permissions object belongs to
* @return Optional<Permissions> with matching entity
*/
public Optional<Permissions> getPermissionsOfEntityOptional(long entityId) {
var permissions = permissionsDAO.findByEntityNeo4jId(entityId);
if (permissions == null) {
Log.errorf("Permissions with entity id %s is null", entityId);
return Optional.empty();
}
return Optional.of(permissions);
}
/**
* Searches for permissions in Neo4j.
*
* This function does perform a check if the user is allowed to query the permissions of an entity.
*
* @param entityId identifies the entity that the permissions object belongs to
* @return Permissions with matching entity
* @throws NotFoundException if permission could not be found
* @throws InvalidAuthException if user has to rights to read permissions
*/
public Permissions getPermissionsOfEntity(long entityId) {
User user = userService.getCurrentUser();
isAccessTypeAllowedForUser(entityId, AccessType.Manage, user.getUsername());
return getPermissionsOfEntityOptional(entityId).orElseThrow(() ->
new NotFoundException(String.format("Permissions with entity %s is null", entityId))
);
}
/**
* @param entityId identifies the entity on which the user has the roles
* @param username the user whose roles are checked
* @return an object describing the roles of the user on the entity
*/
public Roles getUserRolesOnEntity(long entityId, String username) {
var perms = getPermissionsOfEntityOptional(entityId);
return getRoles(perms, username);
}
/**
* Check whether a request is allowed or not
*
* @param entityId the entity that is to be accessed
* @param accessType the access type (read, write, manage)
* @param username the user that wants access
* @return whether the access is allowed or not
*/
public boolean isAccessTypeAllowedForUser(long entityId, AccessType accessType, String username) {
String cacheKey = String.format("%s,%s,%s", entityId, accessType.toString(), username);
if (permissionLastSeenCache.isKeyCached(cacheKey)) return true;
Roles userRolesOnEntity = getUserRolesOnEntity(entityId, username);
boolean isAllowed;
if (userRolesOnEntity.isOwner()) {
isAllowed = true;
} else {
isAllowed = switch (accessType) {
case Read -> userRolesOnEntity.isReader() || userRolesOnEntity.isWriter() || userRolesOnEntity.isManager();
case Write -> userRolesOnEntity.isWriter() || userRolesOnEntity.isManager();
case Manage -> userRolesOnEntity.isManager();
case None -> false;
};
}
if (isAllowed) {
permissionLastSeenCache.cacheKey(cacheKey);
}
return isAllowed;
}
/**
* Checks if the current user is owner of the object specified by its entity id.
*
* @param entityId
* @return boolean, true if current user is owner
* @throws InvalidRequestException if user could not be extracted from authentication context
*/
public boolean isCurrentUserOwner(long entityId) {
Roles roles = getUserRolesOnEntity(entityId, userService.getCurrentUser().getUsername());
return roles.isOwner();
}
/**
* Updates the Permissions in Neo4j
*
* @param permissionsIo the new Permissions object
* @param id identifies the entity
* @return the updated Permissions object
*/
public Permissions updatePermissionsByNeo4jId(PermissionsIO permissionsIo, long id) {
var newPermissions = convertPermissionsIO(permissionsIo);
Optional<Permissions> old = getPermissionsOfEntityOptional(id);
if (old.isEmpty()) {
// There is no old permissions object
newPermissions.setEntities(List.of(new BasicEntity(id)));
return permissionsDAO.createOrUpdate(newPermissions);
}
var oldPermissions = old.get();
if (
newPermissions.getOwner() == null ||
newPermissions.getOwner().getUniqueId().equals(oldPermissions.getOwner().getUniqueId())
) {
oldPermissions.setOwner(oldPermissions.getOwner());
} else {
if (!isOwner(oldPermissions, userService.getCurrentUser().getUsername())) {
throw new InvalidAuthException("Action not allowed. Only Owners are allowed to change ownership.");
}
// check that new owner actually exists
userService.getUser(newPermissions.getOwner().getUsername());
oldPermissions.setOwner(newPermissions.getOwner());
}
oldPermissions.setReader(newPermissions.getReader());
oldPermissions.setWriter(newPermissions.getWriter());
oldPermissions.setReaderGroups(newPermissions.getReaderGroups());
oldPermissions.setWriterGroups(newPermissions.getWriterGroups());
oldPermissions.setManager(newPermissions.getManager());
oldPermissions.setPermissionType(newPermissions.getPermissionType());
var res = permissionsDAO.createOrUpdate(oldPermissions);
return res;
}
public boolean deletePermissions(Permissions permissions) {
return permissionsDAO.deleteByNeo4jId(permissions.getId());
}
/**
* Checks if the request is allowed based on access type and user name. The check is performed by checking the path segments, and request body.
* @param requestContext
* @param accessType
* @param userName
*/
public boolean isAllowed(ContainerRequestContext requestContext, AccessType accessType, String userName) {
List<PathSegment> pathSegments = requestContext.getUriInfo().getPathSegments();
var idSegment = pathSegments.size() > 1 ? pathSegments.get(1).getPath() : null;
// migration state endpoints
if (pathSegments.get(0).getPath().equals("temp") && pathSegments.get(1).getPath().equals("migrations")) {
return true;
}
// Paths with length 1
if (idSegment == null || idSegment.isBlank()) {
// No id in path
return true;
}
// lab journal entries
if (pathSegments.get(0).getPath().equals(Constants.LAB_JOURNAL_ENTRIES)) {
// Lab journal permissions are already checked inside LabJournalEntryService
return true;
}
// users, apiKeys, subscriptions
if (pathSegments.get(0).getPath().equals(Constants.USERS)) {
// Permissions are already checked inside User- ApiKey- and SubscriptionService
return true;
}
// entity paths
if (StringUtils.isNumeric(idSegment)) {
var entityId = Long.parseLong(idSegment);
return isAccessTypeAllowedForUser(entityId, accessType, userName);
}
// usersearch and containersearch
if (
pathSegments.get(0).getPath().equals(Constants.SEARCH) &&
List.of(Constants.USERS, Constants.CONTAINERS, Constants.COLLECTIONS, Constants.USERGROUPS).contains(
pathSegments.get(1).getPath()
) &&
pathSegments.size() == 2
) {
return true;
}
return false;
}
private Set<String> fetchUserNames(List<UserGroup> userGroups) {
Set<String> ret = new HashSet<>();
for (UserGroup userGroup : userGroups) {
Optional<UserGroup> fullUserGroup = userGroupService.getUserGroupOptional(userGroup.getId());
if (fullUserGroup.isPresent()) {
for (User user : fullUserGroup.get().getUsers()) {
ret.add(user.getUsername());
}
}
}
return ret;
}
private Roles getRoles(Optional<Permissions> perms, String username) {
if (perms.isEmpty()) {
// Legacy entity without permissions
return new Roles(false, true, true, true);
}
var roles = new Roles(
isOwner(perms.get(), username),
isManager(perms.get(), username),
isWriter(perms.get(), username),
isReader(perms.get(), username)
);
return roles;
}
private boolean isOwner(Permissions perms, String username) {
return perms.getOwner() != null && username.equals(perms.getOwner().getUsername());
}
private boolean isManager(Permissions perms, String username) {
return perms.getManager().stream().anyMatch(u -> username.equals(u.getUsername()));
}
private boolean isReader(Permissions perms, String username) {
var pub = PermissionType.Public.equals(perms.getPermissionType());
var pubRead = PermissionType.PublicReadable.equals(perms.getPermissionType());
var reader = perms.getReader().stream().anyMatch(u -> username.equals(u.getUsername()));
var readerGroup = fetchUserNames(perms.getReaderGroups()).contains(username);
return pub || pubRead || reader || readerGroup;
}
private boolean isWriter(Permissions perms, String username) {
var pub = PermissionType.Public.equals(perms.getPermissionType());
var writer = perms.getWriter().stream().anyMatch(u -> username.equals(u.getUsername()));
var writerGroup = fetchUserNames(perms.getWriterGroups()).contains(username);
return pub || writer || writerGroup;
}
/**
* Fetches all missing data and transforms PermissionsIO to a Permissions object.
*/
private Permissions convertPermissionsIO(PermissionsIO permissions) {
var owner = permissions.getOwner() != null
? userService.getUserOptional(permissions.getOwner()).orElseGet(null)
: null;
var permissionType = permissions.getPermissionType();
var reader = fetchUsers(permissions.getReader());
var writer = fetchUsers(permissions.getWriter());
var readerGroups = fetchUserGroups(permissions.getReaderGroupIds());
var writerGroups = fetchUserGroups(permissions.getWriterGroupIds());
var manager = fetchUsers(permissions.getManager());
return new Permissions(owner, reader, writer, readerGroups, writerGroups, manager, permissionType);
}
private List<User> fetchUsers(String[] usernames) {
var result = new ArrayList<User>(usernames.length);
for (var username : usernames) {
if (username == null) {
continue;
}
Optional<User> user = userService.getUserOptional(username);
user.ifPresent((User u) -> result.add(u));
}
return result;
}
private List<UserGroup> fetchUserGroups(long[] userGroupIds) {
var result = new ArrayList<UserGroup>(userGroupIds.length);
for (var userGroupId : userGroupIds) {
Optional<UserGroup> userGroup = userGroupService.getUserGroupOptional(userGroupId);
if (userGroup.isPresent()) {
result.add(userGroup.get());
}
}
return result;
}
}