Wiki

Clone wiki

ieeg / ExtensiblePermissions

Extensible Permissions

Extensible permissions are three tables in the database that you can add your own permissions into. The three tables/entities are : permission/Permission, mode/ModeEntity, and domain/DomainEntity. A domain contains modes which contain permissions.

A domain is the type that the permissions apply to - for example you might need to define a permission that makes sense for a Dataset and not a Tool, say can-derive. You can define your own domains - they're just strings when you get down to it.

Mode was invented so that we know when to display permissions. It also functions as a namespace for permission names. For example, we use the "CORE" mode for permissions that are absolute/central and are checked in an IDataSnapshotServer. If you make custom permissions that only apply in a plug-in that you've written, we wouldn't want to display them in the base permission dialogue - so you'd make your own mode.

If you wish to create your own permissions, you'll need to add them to the database by getting SQL executed against the database. There is no service for that.

You use edu.upenn.cis.braintrust.datasnapshot.IDataSnapshotServer.getPermissions(User user, HasAclType entityType, String mode, String targetId, boolean useDatasetShortCache) to retrieve permissions of a particular mode that apply to a particular target and user. See the javadoc.

In order to check your permissions, you can define an IExtAuthzHandler. We use one to check the core permissions called ExtAuthzHandler which you can use as an example. Having said that, they are custom permissions, so do with them what you will.

IExtAuthzHandler has two methods, checkPermitted and isPermitted. They both fundamentally do the same thing, but checkPermitted will throw an exception for you if the attemptedAction is not legal given the perms.

public interface IExtAuthzHandler {

    void checkPermitted(
            ExtPermissions perms,
            Action attemptedAction)
            throws AuthorizationException;

    boolean isPermitted(
            ExtPermissions perms,
            Action attemptedAction);

}

Creating a new permission

Let's go through an example of how we'd create a new permission.

Here's the permission table:

CREATE TABLE `permission` (
  `permission_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `obj_version` int(11) DEFAULT NULL,
  `name` varchar(255) NOT NULL,
  `display_string` varchar(255) NOT NULL,
  `display_order` int(11) NOT NULL,
  `world_perm` bit(1) NOT NULL,
  `project_group_perm` bit(1) NOT NULL,
  `user_perm` bit(1) NOT NULL,
  `description` varchar(4000) DEFAULT NULL,
  `mode_id` bigint(20) NOT NULL,
  PRIMARY KEY (`permission_id`),
  UNIQUE KEY `uq_permission_name_mode_id` (`name`,`mode_id`),
  UNIQUE KEY `uq_permission_display_string_mode_id` (`display_string`,`mode_id`),
  UNIQUE KEY `uq_permission_display_order_mode_id` (`display_order`,`mode_id`),
  KEY `fk_permission_mode` (`mode_id`),
  CONSTRAINT `fk_permission_mode` FOREIGN KEY (`mode_id`) REFERENCES `mode` (`mode_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

user_perm whether or not this permission is applicable to users. 1 is true, 0 is false.

project_group_perm whether or not this permission is applicable to project groups. 1 is true, 0 is false.

world_perm whether or not this permission is applicable to the world. 1 is true, 0 is false.

display_order dictates the ordering of the permission maps which come out of IDataSnapshotServer.getAcesResponse(User, HasAclType, String, String)

name is the name of the permission which is unique in the mode. It is used to identify the permission with increased readability vs. permission_id.

display_string is for display to the user and is unique in the mode.

description is an optional field which is not currently used.

mode_id is the id of the permission's parent mode.

permission_id and obj_version are handled automatically and should not be assigned values.

Here is an example just so we see some concrete values. Internally we use Liquibase to handle these types of database changes. So if we wanted to add a new READ_METADATA permission to the core permissions we would use something like the following changeSet as part of a Liquibase changelog script.

    <changeSet
        id="190"
        author="samd">
        <insert tableName="permission">
            <column
                name="mode_id"
                valueNumeric="1" />
            <column
                name="name"
                value="READ_METADATA" />
            <column
                name="display_string"
                value="Can read metadata" />
            <column
                name="display_order"
                valueNumeric="-100" />
            <column
                name="world_perm"
                valueBoolean="true" />
            <column
                name="project_group_perm"
                valueBoolean="false" />
            <column
                name="user_perm"
                valueBoolean="false" />
        </insert>
    </changeSet>

ACLs

There are currently two domains which can be targets of ACLs: datasets and tools. Adding a new ACL target type requires database schema changes. An ACL is a collection of access control entities (ACE). We support three types of ACE: user, project, and world.

The class for a user ACE is ExtUserAce. ExtUserAce.getUserId() tells you the id of the user to which the ACE applies. (There is also ExtUserAce.getName() if you need the user's name.) An ACL has at most one user ACE for a given user. ExtUserAce.getTargetId() gives you the id of the target of the ACE. That is, the id of the dataset or tool to which the ACE applies. ExtUserAce.getPerms() gives you the set of permissions the ACE assigns to the user on the target.

The class for a project ACE is ExtProjectAce. Every project has two user groups associated with it: the team and the admins. The project ACE assigns permissions on the target to the group. So there is ExtProjectAce.getProjectGroup() which tells you the ACE's project group. An ACL has at most one project ACE for a given project group. As with the user ACE there is ExtProjectAce.getTargetId() and ExtProjectAce.getPerms() to get the target's id and the actual permissions assigned in the ACE.

The class for a world ACE is ExtWorldAce. An ACL has a single world ACE with a (possibly empty) set of permissions. It has the usual getTargetId() and getPerms() methods. There is no user or group associated with the world ACE since it is intended to apply to any user not otherwise covered by another ACE in the ACL.

Viewing an ACL

You can get the permissions of a particular mode for a target by calling IDataSnapshotServer.getAcesResponse(User user, HasAclType domain, String mode, String targetId). This method returns the ACL as an instance of GetAcesResponse. The ACL is grouped by the three ACE types. The methods for accessing the ACL are GetAcesResponse.getUserAces(), GetAcesResponse.getProjectAces(), and GetAcesResponse.getWorldAce(). The returned user and project ACES will not have empty permission sets. If a user or project group has no permissions of the given mode in the ACL then no ACE will appear for them in the response. The world ACE may have an empty set of permissions.

The returned GetAcesResponse also contains other information which assists in displaying the ACL to a user. The list returned by getUserAces() is ordered by the user names and the one returned by getProjectAces() is ordered by the project group names. There is a flag to indicate if user has permission to modify the ACL of the target. The returned object also contains all the permissions of the given mode organized into three maps which can be accessed from GetAcesResponse.getDisplayStrToUserPerm(), GetAcesResponse.getDisplayStrToProjectGroupPerm(), and GetAcesResponse.getDisplayStrToWorldPerm(). As these names suggest the permissions are grouped into maps based on the type of ACE in which they may appear. The keys are the permissions' display strings. The iteration order of these maps is determined the display_order of the permissions. We use this information when creating PermsPresenter, the presenter for our dataset ACL editor GUI which handles the core permissions.

Editing an ACL

The permissions of a particular mode for a target may be edited by calling IDataSnapshotServer.editAcl(User user, HasAclType domain, String mode, List<IEditAclAction<?>> actions). Here are the supported actions.

Actions on a user ACE: * NewUserAce implements IEditAclAction<ExtUserAce> - Adds a new user ACE to the ACL. * UpdateUserAce implements IEditAclAction<ExtUserAce> - Changes the permissions in an existing user ACE. * RemoveUserAce implements IEditAclAction<ExtUserAce> - Removes a user ACE from the ACL.

Actions on a project ACE: * NewProjectAce implements IEditAclAction<ExtProjectAce> - Adds a new project ACE to the ACL. * UpdateProjectAce implements IEditAclAction<ExtProjectAce> - Changes the permissions in an existing project ACE. * RemoveProjectAce implements IEditAclAction<ExtProjectAce> - Removes a project ACE from the ACL.

Actions on the world ACE. * UpdateWorldAce implements IEditAclAction<ExtWorldAce> - Changes the permissions on the world ACE.

There is no New or Update for the world ACE since an ACL always has exactly one world ACE.

Working with permissions and ACLs

An overview of classes used to work with permissions and ACLs in the back end.

edu.upenn.cis.braintrust.dao.IPermissionDAO

The permission DAO has methods which retrieve permission entities from the database by name. There are also convenience methods for the core permissions.

edu.upenn.cis.braintrust.dao.SecurityUtil

This class has static methods for creating the two commonly used ExtAclEntity instances:

  • ExtAclEntity createUserOwnedAcl(UserEntity, IPermissionDAO). The returned ACL has a user ACE for the given user which provides the core owner permission.
  • ExtAclEntity createUserOwnedWorldReadableAcl(UserEntity, IPermissionDAO). The returned ACL includes core read permission in the world ACE in addition to the user owner ACE.

edu.upenn.cis.braintrust.security.ExtPermissionAssembler

This class is used in the backend to convert between the Hibernate entities PerimissionEntity and the DTO used in the rest of the application ExtPermission.

edu.upenn.cis.braintrust.testhelper.TstObjectFactory

This is a factory only used in tests to create all kinds of objects, not just permissions and ALCs. It is in src/main only so that it can be easily used by tests in dependent submodules. If permissions and ACLs are part of your tests you should instantiate one using the constructor with PermissionEntity arguments so that it has access to the core read and owner permissions. Like so

                        final IPermissionDAO permDAO = new PermissionDAOHibernate(session);
            objFac = new TstObjectFactory(
                    permDAO.findStarCoreReadPerm(),
                    permDAO.findStarCoreOwnerPerm());

Submodules which have database tests include an import.sql file which makes sure that the core permissions are automatically added to the H2 database used by the tests. So those permDAO calls should find the correct permissions without further setup in your tests. If you use this constructor then calls to

objFac.newExtAclEntity(UserEntity)
objFac.newExtAclEntityUserOwnedAndWorldReadable(UserEntity)
objFac.newExtAclEntityWorldReadable()
will return ExtAclEntity instances with usable permissions.

Updated