Role based data access support

This commit is contained in:
Fabio Sinibaldi 2022-03-24 17:44:00 +01:00
parent f0d04b8c62
commit 17839745aa
9 changed files with 285 additions and 38 deletions

View File

@ -1,8 +1,12 @@
package org.gcube.application.geoportal.common.model.document.accounting;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import java.util.Objects;
import java.util.Set;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ -15,4 +19,19 @@ public class User {
@JsonProperty(USERNAME)
private String username;
@JsonIgnore
private Set<String> roles;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(getUsername(), user.getUsername());
}
@Override
public int hashCode() {
return Objects.hash(getUsername());
}
}

View File

@ -1,8 +1,11 @@
package org.gcube.application.geoportal.common.model.useCaseDescriptor;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import org.bson.Document;
import org.gcube.application.geoportal.common.model.document.Project;
import org.gcube.application.geoportal.common.model.document.accounting.User;
import java.util.List;
@ -55,4 +58,24 @@ public class DataAccessPolicy {
private List<String> roles;
@JsonProperty(ENFORCER)
private PolicyEnforcer enforcer;
@JsonIgnore
public boolean canRead(Project p, User u){
switch(getPolicy().getRead()){
case OWN: return p.getInfo().getCreationInfo().getUser().equals(u);
case ANY: return true;
case NONE:
default : return false;
}
}
@JsonIgnore
public boolean canWrite(Project p, User u){
switch(getPolicy().getWrite()){
case OWN: return p.getInfo().getCreationInfo().getUser().equals(u);
case ANY: return true;
case NONE:
default : return false;
}
}
}

View File

@ -15,7 +15,9 @@ import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.extern.slf4j.XSlf4j;
import org.bson.types.ObjectId;
import org.gcube.application.geoportal.common.model.document.Project;
import org.gcube.application.geoportal.common.model.document.accounting.AccountingInfo;
import org.gcube.application.geoportal.common.model.document.accounting.User;
@Data
@NoArgsConstructor
@ -95,6 +97,19 @@ public class UseCaseDescriptor {
return toReturn;
}
@JsonIgnore
public DataAccessPolicy getMatching(User u){
DataAccessPolicy defaultPolicy = null;
for (DataAccessPolicy dataAccessPolicy : dataAccessPolicies) {
if(dataAccessPolicy.getRoles()==null||dataAccessPolicy.getRoles().isEmpty())
defaultPolicy= dataAccessPolicy;
for (String r : dataAccessPolicy.getRoles())
if (u.getRoles().contains(r))
return dataAccessPolicy;
}
return defaultPolicy;
}
}

View File

@ -76,12 +76,19 @@ public abstract class MongoManager {
public Document getDocById(ObjectId id) {
public Document getDocById(ObjectId id,Document additionalFilter) {
MongoCollection<Document> coll=getCollection();
return coll.find(new Document(mongoIDFieldName(),id)).first();
Document condition =new Document(mongoIDFieldName(),id);
if(additionalFilter!=null)
condition.putAll(additionalFilter);
return coll.find(condition).first();
}
public Document getDocById(ObjectId id) {
return getDocById(id,null);
}
public FindIterable<Document> iterateDoc(Document filter, Document projection) {
MongoCollection<Document> coll=getCollection();
if(filter == null) filter=new Document();

View File

@ -9,10 +9,7 @@ import org.gcube.application.geoportal.common.model.configuration.Configuration;
import org.gcube.application.geoportal.common.model.rest.QueryRequest;
import org.gcube.application.geoportal.common.model.rest.RegisterFileSetRequest;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.application.geoportal.service.model.internal.faults.DeletionException;
import org.gcube.application.geoportal.service.model.internal.faults.InvalidLockException;
import org.gcube.application.geoportal.service.model.internal.faults.ProjectLockedException;
import org.gcube.application.geoportal.service.model.internal.faults.ProjectNotFoundException;
import org.gcube.application.geoportal.service.model.internal.faults.*;
import org.gcube.common.storagehub.model.exceptions.StorageHubException;
import java.io.IOException;
@ -25,25 +22,25 @@ public interface MongoManagerI<T> {
public T registerNew(Document toRegister) throws IOException, StepException, EventException;
// update
public T update(String id,Document toSetDocument) throws IOException, StepException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException;
public T update(String id,Document toSetDocument) throws IOException, StepException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess;
// delete
public void delete(String id,boolean force) throws DeletionException;
public void delete(String id,boolean force) throws DeletionException, InvalidUserRoleException, ProjectLockedException, ProjectNotFoundException, UnauthorizedAccess, JsonProcessingException;
// get By ID
public T getByID(String id) throws IOException, ProjectNotFoundException;
public T getByID(String id) throws IOException, ProjectNotFoundException, InvalidUserRoleException, UnauthorizedAccess;
// query
public Iterable<Document> query(QueryRequest request);
public Iterable<T> filter(QueryRequest request);
public Iterable<Document> query(QueryRequest request) throws InvalidUserRoleException;
public Iterable<T> filter(QueryRequest request) throws InvalidUserRoleException;
public T performStep(String id, String step, Document options) throws IOException, StepException, ProjectLockedException, ProjectNotFoundException, InvalidLockException;
public T performStep(String id, String step, Document options) throws IOException, StepException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess;
public T registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException;
public T deleteFileSet(String id, String destination, Boolean force) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException;
public T registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess;
public T deleteFileSet(String id, String destination, Boolean force) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess;
public Configuration getConfiguration()throws ConfigurationException;
}

View File

@ -28,9 +28,11 @@ import org.gcube.application.geoportal.common.model.document.access.Access;
import org.gcube.application.geoportal.common.model.document.access.AccessPolicy;
import org.gcube.application.geoportal.common.model.document.accounting.AccountingInfo;
import org.gcube.application.geoportal.common.model.document.accounting.PublicationInfo;
import org.gcube.application.geoportal.common.model.document.accounting.User;
import org.gcube.application.geoportal.common.model.document.filesets.RegisteredFile;
import org.gcube.application.geoportal.common.model.document.filesets.RegisteredFileSet;
import org.gcube.application.geoportal.common.model.document.lifecycle.LifecycleInformation;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.DataAccessPolicy;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.Field;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.HandlerDeclaration;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.UseCaseDescriptor;
@ -98,7 +100,7 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
}
protected Project lock(String id,String op) throws ProjectNotFoundException, ProjectLockedException, JsonProcessingException {
protected Project lock(String id,String op) throws ProjectNotFoundException, ProjectLockedException, JsonProcessingException, InvalidUserRoleException, UnauthorizedAccess {
log.trace("Locking {} cause {} ",id,op);
Lock lock = new Lock();
@ -133,7 +135,7 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
} else return Serialization.convert(obj,Project.class);
}
protected Project unlockAndUpdate(Project proj) throws InvalidLockException, ProjectNotFoundException, JsonProcessingException {
protected Project unlockAndUpdate(Project proj) throws InvalidLockException, ProjectNotFoundException, JsonProcessingException, InvalidUserRoleException, UnauthorizedAccess {
log.trace("Unlocking {} lock is {} ",proj.getId(),proj.getLock());
// find one and update
Lock oldLock = proj.getLock();
@ -230,16 +232,28 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
log.info("Obtained id {} ",id);
try {
return getByID(id.toHexString());
}catch (ProjectNotFoundException e){
}catch (ProjectNotFoundException | InvalidUserRoleException | UnauthorizedAccess e){
throw new WebApplicationException("Unexpected exception while registering project ",e);
}
}
@Override
public Project update(String id, Document toSet) throws IOException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException {
public Project update(String id, Document toSet) throws IOException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess {
log.trace("Replacing {} ",toSet);
Project toUpdate=lock(id,"Manual update");
try {
User u = UserUtils.getCurrent().asInfo().getUser();
final DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Registering Fileset for {} [{}] , policy for {} is {} ",id,useCaseDescriptor.getId(),u,policy);
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles " + u.getRoles());
}
if(!policy.canWrite(toUpdate,u)) throw new UnauthorizedAccess("No edit rights on project "+id);
toUpdate.setTheDocument(toSet);
toUpdate.getLifecycleInformation().cleanState();
toUpdate = onUpdate(toUpdate);
@ -258,10 +272,19 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
@Override
public void delete(String id,boolean force) throws DeletionException {
public void delete(String id,boolean force) throws DeletionException, InvalidUserRoleException, ProjectLockedException, ProjectNotFoundException, UnauthorizedAccess, JsonProcessingException {
log.debug("Deleting by ID {}, force {}",id,force);
try{
Project doc =lock(id,"Deletion { force : "+force+"}");
Project doc =lock(id,"Deletion { force : "+force+"}");
User u = UserUtils.getCurrent().asInfo().getUser();
final DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Registering Fileset for {} [{}] , policy for {} is {} ",id,useCaseDescriptor.getId(),u,policy);
// NB cannot check ownership on returned values, must specify filter
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles " + u.getRoles());
}
if(!policy.canWrite(doc,u)) throw new UnauthorizedAccess("No edit rights on project "+id);
// TODO INVOKE LIFECYCLE
@ -281,22 +304,58 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
// replace(asDocumentWithId(concessione), collectionName);
throw e;
}
}catch(Throwable t){
throw new DeletionException("Unable to delete "+id,t);
}
@Override
public Project getByID(String id) throws ProjectNotFoundException, InvalidUserRoleException, UnauthorizedAccess {
User u = UserUtils.getCurrent().asInfo().getUser();
DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Accessing Project {} [{}] , policy for {} is {} ",id,useCaseDescriptor.getId(),u,policy);
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles "+u.getRoles());
}
}
@Override
public Project getByID(String id) throws ProjectNotFoundException{
Document doc=getDocById(asId(id));
Document doc=getDocById(asId(id),
policy==null?null:policy.getEnforcer().getFilter());
if(doc==null) throw new ProjectNotFoundException("No document with ID "+id);
return convert(doc, Project.class);
Project p = convert(doc, Project.class);
if(!policy.canRead(p,u)) throw new UnauthorizedAccess("No access rights on "+id);
return p;
}
@Override
public Iterable<Document> query(QueryRequest queryRequest) {
log.info("Querying {} ",queryRequest);
public Iterable<Document> query(QueryRequest queryRequest) throws InvalidUserRoleException {
LinkedBlockingQueue queue=new LinkedBlockingQueue<Project>();
User u = UserUtils.getCurrent().asInfo().getUser();
final DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Querying {} [{}] , policy for {} is {} ",queryRequest,useCaseDescriptor.getId(),u,policy);
// NB cannot check ownership on returned values, must specify filter
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles " + u.getRoles());
}
if(policy.getPolicy().getRead().equals(DataAccessPolicy.Policy.Type.NONE)) {
log.info("Read is NONE : Returning empty collection");
return queue;
}
// NB cannot check ownership on returned values, must specify filter
Document finalFilter=new Document();
if(queryRequest.getFilter()!=null)
finalFilter.putAll(queryRequest.getFilter());
Document enforcerFilter =policy.getEnforcer().getFilter();
if(enforcerFilter != null)
finalFilter.putAll(enforcerFilter);
if(policy.getPolicy().getRead().equals(DataAccessPolicy.Policy.Type.OWN))
finalFilter.put(Project.INFO+"."+PublicationInfo.CREATION_INFO+"."+AccountingInfo.USER+"."+User.USERNAME,u.getUsername());
queryRequest.setFilter(finalFilter);
log.debug("Final filter is {}",queryRequest.getFilter());
queryDoc(queryRequest).forEach(
(Consumer<? super Document>) (Document d)->{try{
queue.put(d);
@ -306,9 +365,34 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
}
@Override
public Iterable<Project> filter(QueryRequest queryRequest) {
log.info("Searching concessione for filter {} ",queryRequest);
public Iterable<Project> filter(QueryRequest queryRequest) throws InvalidUserRoleException {
LinkedBlockingQueue queue=new LinkedBlockingQueue<Project>();
User u = UserUtils.getCurrent().asInfo().getUser();
final DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Querying {} [{}] , policy for {} is {} ",queryRequest,useCaseDescriptor.getId(),u,policy);
// NB cannot check ownership on returned values, must specify filter
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles " + u.getRoles());
}
if(policy.getPolicy().getRead().equals(DataAccessPolicy.Policy.Type.NONE)) {
log.info("Read is NONE : Returning empty collection");
return queue;
}
// NB cannot check ownership on returned values, must specify filter
Document finalFilter=new Document();
if(queryRequest.getFilter()!=null)
finalFilter.putAll(queryRequest.getFilter());
Document enforcerFilter =policy.getEnforcer().getFilter();
if(enforcerFilter != null)
finalFilter.putAll(enforcerFilter);
if(policy.getPolicy().getRead().equals(DataAccessPolicy.Policy.Type.OWN))
finalFilter.put(Project.INFO+"."+PublicationInfo.CREATION_INFO+"."+AccountingInfo.USER+"."+User.USERNAME,u.getUsername());
queryRequest.setFilter(finalFilter);
log.debug("Final filter is {}",queryRequest.getFilter());
queryDoc(queryRequest).forEach(
(Consumer<? super Document>) (Document d)->{try{
queue.put(d);
@ -319,9 +403,21 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
@Override
public Project performStep(String id, String step, Document options) throws StepException, JsonProcessingException, ProjectLockedException, ProjectNotFoundException, InvalidLockException {
public Project performStep(String id, String step, Document options) throws StepException, JsonProcessingException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess {
Project document = lock(id,"Step "+step+" execution");
try{
User u = UserUtils.getCurrent().asInfo().getUser();
final DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Registering Fileset for {} [{}] , policy for {} is {} ",id,useCaseDescriptor.getId(),u,policy);
// NB cannot check ownership on returned values, must specify filter
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles " + u.getRoles());
}
if(!policy.canWrite(document,u)) throw new UnauthorizedAccess("No edit rights on project "+id);
document.getLifecycleInformation().cleanState();
document = step(document, step, options);
} catch(Throwable t){
@ -356,13 +452,27 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
*
*/
@Override
public Project registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException {
public Project registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess {
log.info("Registering Fileset for {} [useCaseDescriptor ID {}], Request is {} ",id, useCaseDescriptor.getId(),request);
List<TempFile> files=request.getStreams();
Document attributes =request.getAttributes();
Project doc=lock(id,"Register Fileset");
try {
// Checking user rights on proj
User u = UserUtils.getCurrent().asInfo().getUser();
final DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Registering Fileset for {} [{}] , policy for {} is {} ",id,useCaseDescriptor.getId(),u,policy);
// NB cannot check ownership on returned values, must specify filter
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles " + u.getRoles());
}
if(!policy.canWrite(doc,u)) throw new UnauthorizedAccess("No edit rights on project "+id);
doc.getLifecycleInformation().cleanState();
doc.getLifecycleInformation().setLastOperationStatus(LifecycleInformation.Status.OK);
@ -448,12 +558,25 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
@Override
public Project deleteFileSet(String id, String path, Boolean force) throws ConfigurationException, StorageHubException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException {
public Project deleteFileSet(String id, String path, Boolean force) throws ConfigurationException, StorageHubException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess {
log.info("Deleting Fileset for {} [useCaseDescriptor ID {}], at {} [force {} ]",id, useCaseDescriptor.getId(),path,force);
Project doc = lock(id,"Fileset Deletion");
try {
User u = UserUtils.getCurrent().asInfo().getUser();
final DataAccessPolicy policy = useCaseDescriptor.getMatching(u);
log.info("Registering Fileset for {} [{}] , policy for {} is {} ",id,useCaseDescriptor.getId(),u,policy);
// NB cannot check ownership on returned values, must specify filter
if(policy == null) {
log.warn("No policy found for {}. Returning empty ", u);
throw new InvalidUserRoleException("No policy defined for current user roles " + u.getRoles());
}
if(!policy.canWrite(doc,u)) throw new UnauthorizedAccess("No edit rights on project "+id);
doc.getLifecycleInformation().cleanState();
doc.getLifecycleInformation().cleanState().setLastOperationStatus(LifecycleInformation.Status.OK);
@ -530,6 +653,8 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI<
try{
log.info("[UseCaseDescriptor {}] Invoking Step {} on {}" , useCaseDescriptor.getId(),step,getManager().getDescriptor());
AccountingInfo user= UserUtils.getCurrent().asInfo();
StepExecutionRequest request=new StepExecutionRequest(useCaseDescriptor,user.getUser(),user.getContext(),theDocument,step);
log.debug("Requesting Step Execution {}",request);

View File

@ -0,0 +1,23 @@
package org.gcube.application.geoportal.service.model.internal.faults;
public class InvalidUserRoleException extends Exception {
public InvalidUserRoleException() {
}
public InvalidUserRoleException(String message) {
super(message);
}
public InvalidUserRoleException(String message, Throwable cause) {
super(message, cause);
}
public InvalidUserRoleException(Throwable cause) {
super(cause);
}
public InvalidUserRoleException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,23 @@
package org.gcube.application.geoportal.service.model.internal.faults;
public class UnauthorizedAccess extends Exception {
public UnauthorizedAccess() {
}
public UnauthorizedAccess(String message) {
super(message);
}
public UnauthorizedAccess(String message, Throwable cause) {
super(message, cause);
}
public UnauthorizedAccess(Throwable cause) {
super(cause);
}
public UnauthorizedAccess(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -13,6 +13,9 @@ import org.gcube.common.authorization.library.provider.SecurityTokenProvider;
import org.gcube.common.scope.api.ScopeProvider;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@Slf4j
public class UserUtils {
@ -29,7 +32,11 @@ public class UserUtils {
}catch(Throwable e) {
log.warn("Unable to get client info ",e);
}
AuthenticatedUser toReturn = new AuthenticatedUser(client, AccessTokenProvider.instance.get(),SecurityTokenProvider.instance.get(),context);
//TODO Actually get ROLES
Set<String> roles = new HashSet<>();
AuthenticatedUser toReturn =
new AuthenticatedUser(client,roles, AccessTokenProvider.instance.get(),SecurityTokenProvider.instance.get(),context);
log.info("Current User is {} ",toReturn);
return toReturn;
@ -40,6 +47,9 @@ public class UserUtils {
@Getter
public static class AuthenticatedUser {
private ClientInfo user;
private Set<String> roles;
private String uma_token;
private String gcube_token;
@ -57,6 +67,9 @@ public class UserUtils {
builder.append(", gcube_token=");
builder.append(gcube_token==null?gcube_token:"***");
builder.append(", roles=");
builder.append(roles);
builder.append(", context=");
builder.append(context);
builder.append("]");
@ -68,9 +81,11 @@ public class UserUtils {
User user = new User();
try{
user.setUsername(this.getUser().getId());
user.setRoles(roles);
}catch(Exception e){
log.warn("Unable to determine user id, using FAKE");
user.setUsername("FAKE");
user.setRoles(Collections.EMPTY_SET);
}
info.setUser(user);