You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.
Good article about ACL Security in Seam: http://java.dzone.com/articles/acl-security-in-seam
One of the most important aspects described in this article - a way to store permissions in database. Offered way is to use AccountPermission entity. By default, bind to the target entity done via TARGET field. The values in this field have the form <class name>:<entity ID>
. This values puts in database.
We tried to use this approach in its pure form, and faced with some integration problems. It was the task of checking the rights outside - had to build a report in MS SQL Reporter. It was necessary to restrict user access to entities, shown in this report.
There was a problem with that, in order to determine the authorized entity it is required to perform TARGET parsing - split it by the symbol :
to extract entity ID
and use this ID in the SQL-query. This method has proved very difficult to implement and we went on another path.
We have expanded AccountPermission in a way to store class name
and entity ID
apart:
@Entity @Table(name = "ACCOUNT_PERMISSION") public class AccountPermission extends Permission { /*===========================================[ STATIC VARIABLES ]=============*/ private static final long serialVersionUID = -8987706089479804254L; /*===========================================[ INSTANCE VARIABLES ]=========*/ @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @PermissionUser @PermissionRole @Column(name = "RECIPIENT") private String recipient; @Column(name = "TARGET") private String permissionTarget; @PermissionAction @Column(name = "ACTION") private String action; @PermissionDiscriminator @Column(name = "DISCRIMINATOR") private String discriminator; @Column(name = "TARGET_ID") private long targetID; @Column(name = "TARGET_CLASS") private String targetClass; /*===========================================[ CONSTRUCTORS ]===============*/ public AccountPermission() { super(null, null, null); } public AccountPermission(Object target, String action, Principal principal) { super(target, action, principal); this.action = action; } /*===========================================[ CLASS METHODS ]==============*/ @PermissionTarget public String getPermissionTarget() { return permissionTarget; } public void setPermissionTarget(String permissionTarget) { // This method called from JpaPermissionStore. We need to split permissionTarget by ':' this.permissionTarget = permissionTarget; // Works only if we use EntityIdentifierStrategy String[] targetParts = permissionTarget.split(":"); if (targetParts.length == 2) { targetClass = targetParts[0]; targetID = Long.parseLong(targetParts[1]); } } public String getAction() { return action; } public String getDiscriminator() { return discriminator; } public String getTargetClass() { return targetClass; } public long getTargetID() { return targetID; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof AccountPermission)) { return false; } AccountPermission permission = (AccountPermission) obj; if (action != null ? !action.equals(permission.action) : permission.action != null) { return false; } if (discriminator != null ? !discriminator.equals(permission.discriminator) : permission.discriminator != null) { return false; } if (recipient != null ? !recipient.equals(permission.recipient) : permission.recipient != null) { return false; } if (permissionTarget != null ? !permissionTarget.equals(permission.permissionTarget) : permission.permissionTarget != null) { return false; } return !(targetClass != null ? !targetClass.equals(permission.targetClass) : permission.targetClass != null); } @Override public int hashCode() { int result = recipient != null ? recipient.hashCode() : 0; result = 31 * result + (permissionTarget != null ? permissionTarget.hashCode() : 0); result = 31 * result + (action != null ? action.hashCode() : 0); result = 31 * result + (discriminator != null ? discriminator.hashCode() : 0); result = 31 * result + (targetClass != null ? targetClass.hashCode() : 0); return result; } @Override public String toString() { return "AccountPermission{" + "recipient='" + recipient + '\'' + ", target='" + permissionTarget + '\'' + ", action='" + action + '\'' + '}'; } }
The main thing is done in the method setPermissionTarget. Here comes the separation of TARGET on TARGET_CLASS and TARGET_ID. It also shows that AccountPermission extends Permission. This allows you to work beautifully with it through PermissionManager component:
final Role recipient = new Role("hyperic_extensions_admin"); new RunAsOperation(true) { @Override public void execute() { permissionManager.grantPermission(new AccountPermission(metric, PermissionManager.PERMISSION_READ, recipient)); permissionManager.grantPermission(new AccountPermission(metric, PermissionManager.PERMISSION_GRANT, recipient)); permissionManager.grantPermission(new AccountPermission(metric, PermissionManager.PERMISSION_REVOKE, recipient)); permissionManager.grantPermission(new AccountPermission(metric, PermissionActions.MANAGE.toString(), recipient)); } }.run();
The important thing - in order to field TARGET_CLASS have correct values it's needed to tag the entities, which hang up permissions, with @Identifier annotation with the 'name' parameter. This parameter should have a value equal to the class name. In this case, AccountPermission, PemissionManager and JpaPermissionStore will work like clockwork:
@Permissions({ @Permission(action = "manage") }) @Entity @Identifier(name = "com.company.foo.Metric") @Table(name = "METRIC") public class Metric implements Serializable { ... }