Commit 4c069b8c authored by Matija Obreza's avatar Matija Obreza

ACL management made easier

- Serialize List<AclEntry> as List<Permissions>
- Use Permissions and SidPermissions instead of Map<?,?>
parent 4f249516
......@@ -20,7 +20,7 @@ import java.io.Serializable;
import com.fasterxml.jackson.databind.annotation.JsonAppend;
import org.genesys.blocks.model.EntityId;
import org.genesys.blocks.security.serialization.CurrentPermissions;
import org.genesys.blocks.security.serialization.Permissions;
import org.genesys.blocks.security.serialization.CurrentPermissionsWriter;
import org.genesys.blocks.util.JsonClassNameWriter;
......@@ -28,6 +28,6 @@ import org.genesys.blocks.util.JsonClassNameWriter;
* Interface label for entities that require ACL security.
*/
@JsonAppend(props = { @JsonAppend.Prop(name = "_type", value = JsonClassNameWriter.class, type = String.class),
@JsonAppend.Prop(name = "_permissions", value = CurrentPermissionsWriter.class, type = CurrentPermissions.class) })
@JsonAppend.Prop(name = "_permissions", value = CurrentPermissionsWriter.class, type = Permissions.class) })
public interface AclAwareModel extends Serializable, EntityId {
}
......@@ -56,8 +56,8 @@ public class AclEntry extends BasicModel {
private long aceOrder;
/** The mask. */
@Column(name = "mask", nullable = false, length = 11)
private long mask;
@Column(name = "mask", nullable = false)
private int mask;
/** The granting. */
@Column(name = "granting", nullable = false, length = 1)
......@@ -130,7 +130,7 @@ public class AclEntry extends BasicModel {
*
* @return the mask
*/
public long getMask() {
public int getMask() {
return mask;
}
......@@ -139,7 +139,7 @@ public class AclEntry extends BasicModel {
*
* @param mask the new mask
*/
public void setMask(final long mask) {
public void setMask(final int mask) {
this.mask = mask;
}
......
......@@ -28,10 +28,12 @@ import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.genesys.blocks.model.BasicModel;
import org.genesys.blocks.security.serialization.AclEntriesToPermissions;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* ACL Object Identity represents a specific ACL-aware entity (combination of
......@@ -73,7 +75,8 @@ public class AclObjectIdentity extends BasicModel {
private boolean entriesInheriting;
/** The acl entries. */
// @JsonIgnore
// @JsonIgnore
@JsonSerialize(converter = AclEntriesToPermissions.class)
@OneToMany(mappedBy = "aclObjectIdentity", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<AclEntry> aclEntries;
......
......@@ -59,7 +59,7 @@ public interface AclEntryPersistence extends JpaRepository<AclEntry, Long> {
* @return - returns lists of user's permissions on domain class
*/
@Query("select ae.aclObjectIdentity.objectIdIdentity from AclEntry ae where ae.aclSid=?1 and ae.aclObjectIdentity.aclClass.aclClass=?2 and ae.mask=?3 and ae.granting=true")
List<Long> findObjectIdentitiesForSidAndAclClassAndMask(AclSid sid, String aclClass, long mask);
List<Long> findObjectIdentitiesForSidAndAclClassAndMask(AclSid sid, String aclClass, int mask);
/**
* Calculates max. ace_order for acl_object_identity to avoid DuplicateIndex
......
......@@ -45,4 +45,12 @@ public interface AclSidPersistence extends JpaRepository<AclSid, Long> {
@Query("select distinct sid from AclSid sid where sid.id in :ids")
List<AclSid> listById(@Param("ids") Iterable<Long> ids);
/**
* List SIDs of authorities (roles)
*
* @return the list of {@link AclSid} for registered authorities
*/
@Query("select sid from AclSid sid where sid.principal = false")
List<AclSid> listAuthoritySids();
}
......@@ -15,23 +15,51 @@
*/
package org.genesys.blocks.security.serialization;
import java.util.List;
import org.genesys.blocks.security.model.AclEntry;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.Converter;
/**
* The Class CurrentPermissions.
* Converts a list of {@link AclEntry} to the easily digestible
* {@link SidPermissions} objects/.
*
* @author Matija Obreza
*/
public class CurrentPermissions {
/** The create. */
public boolean create;
/** The read. */
public boolean read;
/** The write. */
public boolean write;
/** The delete. */
public boolean delete;
/** The manage. */
public boolean manage;
public class AclEntriesToPermissions implements Converter<List<AclEntry>, List<SidPermissions>> {
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.util.Converter#convert(java.lang.Object)
*/
@Override
public List<SidPermissions> convert(List<AclEntry> aclEntries) {
return SidPermissions.fromEntries(aclEntries);
}
/*
* (non-Javadoc)
* @see
* com.fasterxml.jackson.databind.util.Converter#getInputType(com.fasterxml.
* jackson.databind.type.TypeFactory)
*/
@Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructCollectionType(List.class, AclEntry.class);
}
/*
* (non-Javadoc)
* @see
* com.fasterxml.jackson.databind.util.Converter#getOutputType(com.fasterxml.
* jackson.databind.type.TypeFactory)
*/
@Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructCollectionType(List.class, SidPermissions.class);
}
}
......@@ -37,7 +37,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
/**
* The CurrentPermissionsWriter is applied to {@link AclAwareModel} and it
* instructs Jackson to to include {@link CurrentPermissions} for current SID
* instructs Jackson to to include {@link Permissions} for current SID
* for every ACL aware entity.
*
* Serialization is enabled <code>@JsonAppend</code> annotation on
......@@ -110,7 +110,7 @@ public class CurrentPermissionsWriter extends VirtualBeanPropertyWriter {
if (permissionEvaluator != null) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
CurrentPermissions perms = new CurrentPermissions();
Permissions perms = new Permissions();
perms.create = permissionEvaluator.hasPermission(authentication, bean, BasePermission.CREATE);
perms.read = permissionEvaluator.hasPermission(authentication, bean, BasePermission.READ);
perms.write = permissionEvaluator.hasPermission(authentication, bean, BasePermission.WRITE);
......
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys.blocks.security.serialization;
import static org.springframework.security.acls.domain.BasePermission.*;
import org.springframework.security.acls.domain.BasePermission;
/**
* A simple POJO for object permissions.
*/
public class Permissions {
/** Allowed to create */
public boolean create;
/** Allowed to read */
public boolean read;
/** Allowed to write/modify */
public boolean write;
/** Allowed to delete */
public boolean delete;
/** Allowed to admin/manage */
public boolean manage;
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(create ? "c" : "-");
sb.append(read ? "r" : "-");
sb.append(write ? "w" : "-");
sb.append(delete ? "d" : "-");
sb.append(manage ? "A" : "-"); // Admin
return sb.toString();
}
/**
* Is Permissions granting the {@link BasePermission} by its mask?
*
* @param mask int value of Permission mask
* @return true if granting, false otherwise
* @throws UnsupportedOperationException if mask is not understood
*/
public boolean isGranting(int mask) {
if (CREATE.getMask() == mask) {
return create;
} else if (READ.getMask() == mask) {
return read;
} else if (WRITE.getMask() == mask) {
return write;
} else if (DELETE.getMask() == mask) {
return delete;
} else if (ADMINISTRATION.getMask() == mask) {
return manage;
} else {
throw new UnsupportedOperationException("No such permission with mask=" + mask);
}
}
public Permissions grantAll() {
this.create = true;
this.read = true;
this.write = true;
this.delete = true;
this.manage = true;
return this;
}
public Permissions grantNone() {
this.create = false;
this.read = false;
this.write = false;
this.delete = false;
this.manage = false;
return this;
}
}
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys.blocks.security.serialization;
import static org.springframework.security.acls.domain.BasePermission.*;
import java.util.ArrayList;
import java.util.List;
import org.genesys.blocks.security.model.AclEntry;
import org.genesys.blocks.security.model.AclSid;
import com.google.common.collect.Lists;
/**
* Simple POJO for SID's permissions
*/
public class SidPermissions extends Permissions {
/** SID having these permisions */
public AclSid sid;
/**
* Acl sid.
*
* @param aclSid the acl sid
* @return the sid permissions
*/
public SidPermissions aclSid(AclSid aclSid) {
this.sid = aclSid;
return this;
}
/*
* (non-Javadoc)
* @see org.genesys.blocks.security.serialization.Permissions#toString()
*/
@Override
public String toString() {
return "sid=" + sid + " p=" + super.toString();
}
/**
* Convert List of {@link AclEntry} to List of {@link SidPermissions}.
*
* @param aclEntries the acl entries
* @return the list
*/
public static List<SidPermissions> fromEntries(List<AclEntry> aclEntries) {
if (aclEntries == null) {
return null;
}
if (aclEntries.isEmpty()) {
return Lists.newArrayList();
}
List<SidPermissions> converted = new ArrayList<>(1 + aclEntries.size() / 5);
for (final AclEntry entry : aclEntries) {
// find existing entry
SidPermissions permission = converted.stream().filter(p -> entry.getAclSid().equals(p.sid)).findFirst().orElse(null);
// or make one
if (permission == null) {
permission = new SidPermissions().aclSid(entry.getAclSid());
converted.add(permission);
}
if (CREATE.getMask() == entry.getMask()) {
permission.create = entry.isGranting();
} else if (READ.getMask() == entry.getMask()) {
permission.read = entry.isGranting();
} else if (WRITE.getMask() == entry.getMask()) {
permission.write = entry.isGranting();
} else if (DELETE.getMask() == entry.getMask()) {
permission.delete = entry.isGranting();
} else if (ADMINISTRATION.getMask() == entry.getMask()) {
permission.manage = entry.isGranting();
}
}
return converted;
}
}
......@@ -22,6 +22,8 @@ import org.genesys.blocks.security.model.AclAwareModel;
import org.genesys.blocks.security.model.AclEntry;
import org.genesys.blocks.security.model.AclObjectIdentity;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.serialization.Permissions;
import org.genesys.blocks.security.serialization.SidPermissions;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.model.Permission;
......@@ -55,6 +57,13 @@ public interface CustomAclService {
*/
AclSid getAuthoritySid(String authority);
/**
* List authority sids.
*
* @return the list of {@link AclSid} for authorities
*/
List<AclSid> listAuthoritySids();
/**
* Adds the creator permissions.
*
......@@ -112,7 +121,7 @@ public interface CustomAclService {
* @return the permissions
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#id, #className, 'ADMINISTRATION')")
Map<String, Map<Integer, Boolean>> getPermissions(long id, String className);
List<SidPermissions> getPermissions(long id, String className);
/**
* Gets the permissions.
......@@ -121,7 +130,7 @@ public interface CustomAclService {
* @return the permissions
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(entity, 'ADMINISTRATION')")
Map<String, Map<Integer, Boolean>> getPermissions(AclAwareModel entity);
List<SidPermissions> getPermissions(AclAwareModel entity);
/**
* Gets the acl entries.
......@@ -138,10 +147,10 @@ public interface CustomAclService {
* @param entity the entity
* @param sid the sid
* @param permissionMap the permission map
* @return
* @return
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(entity, 'ADMINISTRATION')")
List<AclEntry> updatePermissions(AclAwareModel entity, AclSid sid, Map<Integer, Boolean> permissionMap);
List<AclEntry> updatePermissions(AclAwareModel entity, AclSid sid, final Permissions permissions);
/**
* Update permissions.
......@@ -149,10 +158,10 @@ public interface CustomAclService {
* @param objectIdentity the object identity
* @param sid the sid
* @param permissionMap the permission map
* @return
* @return
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#objectIdentity.objectIdIdentity, #objectIdentity.aclClass.aclClass, 'ADMINISTRATION')")
List<AclEntry> updatePermissions(AclObjectIdentity objectIdentity, AclSid sid, Map<Integer, Boolean> permissionMap);
List<AclEntry> updatePermissions(AclObjectIdentity objectIdentity, AclSid sid, final Permissions permissions);
/**
* Gets the acl entries.
......@@ -191,19 +200,19 @@ public interface CustomAclService {
* @param permissions the permissions
* @return list of new ACL entries
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#id, #className, 'ADMINISTRATION')")
List<AclEntry> addPermissions(long id, String className, AclSid sid, Map<Integer, Boolean> permissions);
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#objectIdentity.objectIdIdentity, #objectIdentity.aclClass.aclClass, 'ADMINISTRATION')")
List<AclEntry> addPermissions(AclObjectIdentity objectIdentity, AclSid sid, final Permissions permissions);
/**
* Adds the permissions.
*
* @param entity the entity
* @param sid the sid
* @param permissionMap the permission map
* @param permissions the permissions
* @return list of new ACL entries
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#entity, 'ADMINISTRATION')")
List<AclEntry> addPermissions(AclAwareModel entity, AclSid sid, Map<Integer, Boolean> permissionMap);
List<AclEntry> addPermissions(AclAwareModel entity, AclSid sid, final Permissions permissions);
/**
* Ensure object identity.
......@@ -225,11 +234,5 @@ public interface CustomAclService {
*/
List<Long> listObjectIdentityIdsForSid(Class<? extends AclAwareModel> clazz, AclSid sid, Permission permission);
/**
* Utility method that creates a non-granting permission map
*
* @return Map with all available permissions, set to false
*/
Map<Integer, Boolean> blankPermissionsMap();
}
......@@ -16,9 +16,7 @@
package org.genesys.blocks.security.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.model.AclAwareModel;
......@@ -30,6 +28,8 @@ import org.genesys.blocks.security.persistence.AclClassPersistence;
import org.genesys.blocks.security.persistence.AclEntryPersistence;
import org.genesys.blocks.security.persistence.AclObjectIdentityPersistence;
import org.genesys.blocks.security.persistence.AclSidPersistence;
import org.genesys.blocks.security.serialization.Permissions;
import org.genesys.blocks.security.serialization.SidPermissions;
import org.genesys.blocks.security.service.CustomAclService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -82,20 +82,6 @@ public class CustomAclServiceImpl implements CustomAclService {
/** The Constant LOG. */
private static final Logger LOG = LoggerFactory.getLogger(CustomAclServiceImpl.class);
/*
* (non-Javadoc)
* @see
* org.genesys.blocks.security.service.CustomAclService#blankPermissionsMap()
*/
@Override
public Map<Integer, Boolean> blankPermissionsMap() {
HashMap<Integer, Boolean> map = new HashMap<>();
for (Permission p : basePermissions) {
map.put(p.getMask(), false);
}
return map;
}
@Override
@Transactional(readOnly = true)
public AclSid getSid(Long id) {
......@@ -107,12 +93,6 @@ public class CustomAclServiceImpl implements CustomAclService {
return ensureSidForAuthority(authority);
}
/*
* (non-Javadoc)
* @see
* org.genesys.blocks.security.service.CustomAclService#addCreatorPermissions(
* org.genesys.blocks.security.model.AclAwareModel)
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addCreatorPermissions(final AclAwareModel target) {
......@@ -147,21 +127,11 @@ public class CustomAclServiceImpl implements CustomAclService {
.getAclClass());
if (savedAclObjectIdentity == null) {
savedAclObjectIdentity = aclObjectIdentityPersistence.save(objectIdentity);
final Map<Integer, Boolean> permissionsMap = new HashMap<>();
for (final Permission permission : basePermissions) {
permissionsMap.put(permission.getMask(), true);
}
addPermissions(ownerSid, savedAclObjectIdentity, permissionsMap);
final Permissions permissions = new Permissions().grantAll();
addPermissions(savedAclObjectIdentity, ownerSid, permissions);
}
}
/*
* (non-Javadoc)
* @see
* org.genesys.blocks.security.service.CustomAclService#removePermissions(org.
* genesys.blocks.security.model.AclAwareModel)
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void removePermissions(final AclAwareModel target) {
......@@ -196,15 +166,17 @@ public class CustomAclServiceImpl implements CustomAclService {
/**
* Adds the permissions.
*
* @param sid the sid
* @param objectIdentity the object identity
* @param sid the sid
* @param permissions the permissions
* @return
* @return the list
*/
private List<AclEntry> addPermissions(final AclSid sid, final AclObjectIdentity objectIdentity, final Map<Integer, Boolean> permissions) {
@Override
@Transactional
public List<AclEntry> addPermissions(final AclObjectIdentity objectIdentity, final AclSid sid, final Permissions permissions) {
try {
List<AclEntry> aclEntries = new ArrayList<>();
long nextAceOrder=getAceOrder(objectIdentity.getId());
long nextAceOrder = getAceOrder(objectIdentity.getId());
// create Acl Entry
for (final Permission permission : basePermissions) {
final int mask = permission.getMask();
......@@ -212,7 +184,7 @@ public class CustomAclServiceImpl implements CustomAclService {
aclEntry.setAclObjectIdentity(objectIdentity);
aclEntry.setAclSid(sid);
aclEntry.setAceOrder(nextAceOrder++);
aclEntry.setGranting(permissions.get(mask));
aclEntry.setGranting(permissions.isGranting(mask));
aclEntry.setAuditSuccess(true);
aclEntry.setAuditFailure(true);
// set full access for own organization
......@@ -329,19 +301,9 @@ public class CustomAclServiceImpl implements CustomAclService {
@Transactional(readOnly = true)
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#id, #className, 'ADMINISTRATION')")
public Map<String, Map<Integer, Boolean>> getPermissions(final long id, final String className) {