3

I'm working on an application that needs access control. Basically, the problem I'm facing is this (heavily simplified):

The application has two main sections:

  1. Files = File[]
  2. Contacts = Contact[]

A File resource can have many contacts assigned to it. Conversely, a Contact resource can be assigned to many files.

What I want to do is create three roles:

  1. Clerk:

    • has create, read, and update permissions, scoped to the Files section.
    • does not have access to Contacts section (i.e. can't create, read, update, or destroy)
  2. Contact manager:

    • has create, read, update, and destroy permissions, scoped to the Contacts section
    • does not have access to Files section (i.e. can't create, read, update, or destroy)
  3. Admin:

    • has create, read, update, and destroy permissions, scoped to Files and Contacts section.

For the third role (Admin), I see no conflicts. The first issue arises when a Contact Manager wants to destroy a Contact resource that is linked to a File resource (that, as a Contact Manager, he/she doesn't have access to).

In addition, I'd like to take the roles one step further, and for example give them certain CRUD permissions for Files as well as Contacts, but have a scope within Files and within Contact (e.g. permissions apply to Files of type X, and Contacts of type Y).

Then other issues start arising: what if a certain role destroys a Contact, that is linked to a File that the role does not have access to? What if a role has access to a subset of Files, and a subset of Contacts, but one of the Files the role has access to, has Contacts linked to it that the role does not have access to?

Is there an elegant way to solve this using the RBAC model? I've also looked into ARBAC, but it seems like overkill for a relatively simple application. To me, it seems that the root of all the issues is the many-to-many relationship between Files and Contacts.

Are there specific ways to deal with a situation like this? I'm fairly new to this, and these permissions just landed in my lap for this project. I've looked into several Access Control models (even OrBAC), but none seem to be able to address this issue.

Any help or hints in to the right direction would be greatly appreciated.

2

Your data model looks like a graph. Contact --- assigned to --- File, and vice versa. So the assignments are links (edges) in the graph.

I am assuming that there can also be multiple contacts assigned to a single file or multiple files assigned to a contact. How would you handle deleting a contact that is assigned to a file which is assigned to another contact? If you delete a contact which would delete a linked file, then that would leave the other contact "hanging".

I would suggest to treat the assignments i.e. the links (edges) as a scope as well that could have create, destroy, update permissions. Give roles permissions to assignments as well.

You need to make sure that for a contact or file to be destroyed, it should not have any links (assignments) to other file or contact. This will prevent the "hanging" situations described above. So a node can be destroyed only if it does not have any edges.

If a user tries to destroy an object which has links to other object, then throw an error such as "Cannot delete contact which has assignments to other files. Delete the assignments first".

Now, you can assign the CRUD permissions for assignment scope to the Clerk and Contact Manager roles. If a Contact Manager tries to delete a contact with a file assigned to it, then they need to destroy the assignment first (for which they have the destroy permission). Once all the file assignments to that contact are destroyed, the contact can be destroyed. The responsibility of deleting files is anyways with the Clerk which can be deleted provided it does not have any contact assigned.

0

It seems to me that Discretionary Access Control (DAC) is most suited to your requirements. First of all its worth keeping access control policy in configuration and not in your application source code - coding to the activity. For example, (based on an example in the OWASP Top 10 Proactive Controls Project) instead of:

if (user.hasRole("ADMIN")) || (user.hasRole("MANAGER))
    deleteContact();

Consider the following instead:

if (user.hasAccess("CONTACT_DELETE"))
    deleteContact();

In your database you would configure which activity permissions are assigned to each role.

In terms of database table or entity relationships:

  • A security_activity table will include one record for each functional activity.
  • A security_activity may be assigned to zero or more security_role, using a link table.
  • A security_role may be assigned to zero or more security_user, using a link table.

Let's now apply the above to your 3 scenarios:

  1. Clerk:

    • security_activity: id=1; name="FILE_CREATE"
    • security_activity: id=2; name="FILE_READ"
    • security_activity: id=3; name="FILE_UPDATE"
    • security_activity: id=4; name="FILE_DELETE"
    • security_role: id=1; name="Clerk"
    • security_role_activity: id=1; role=1; activity=1
    • security_role_activity: id=2; role=1; activity=2
    • security_role_activity: id=3; role=1; activity=3
    • security_role_activity: id=4; role=1; activity=4
    • security_user: id=1; name="Andrew A."; job_title="Clerk"
    • security_user_role: id=1; user=1; role=1
  2. Contact Manager:

    • security_activity: id=5; name="CONTACT_CREATE"
    • security_activity: id=6; name="CONTACT_READ"
    • security_activity: id=7; name="CONTACT_UPDATE"
    • security_activity: id=8; name="CONTACT_DELETE"
    • security_role: id=2; name="Contact Manager"
    • security_role_activity: id=5; role=2; activity=5
    • security_role_activity: id=6; role=2; activity=6
    • security_role_activity: id=7; role=2; activity=7
    • security_role_activity: id=8; role=2; activity=8
    • security_user: id=2; name="Barry B."; job_title="Contact Manager"
    • security_user_role: id=2; user=2; role=2
  3. Admin:

    • security_role: id=3; name="Admin"
    • security_role_activity: id=9; role=3; activity=1
    • security_role_activity: id=10; role=3; activity=2
    • security_role_activity: id=11; role=3; activity=3
    • security_role_activity: id=12; role=3; activity=4
    • security_role_activity: id=13; role=3; activity=5
    • security_role_activity: id=14; role=3; activity=6
    • security_role_activity: id=15; role=3; activity=7
    • security_role_activity: id=16; role=3; activity=8
    • security_user: id=3; name="Catherine C."; job_title="Admin"
    • security_user_role: id=3; user=3; role=3

"In addition, I'd like to take the roles one step further, and for example give them certain CRUD permissions for Files as well as Contacts, but have a scope within Files and within Contact (e.g. permissions apply to Files of type X, and Contacts of type Y)."

To achieve this you could create extra activity records, for example for type X files:

if ((user.hasAccess("FILE_DELETE")) && (user.hasAccess("FILE_TYPE_X")))
    deleteFile();

And for type Y contacts (remember to assign these additional activities to the roles):

if ((user.hasAccess("CONTACT_DELETE")) && (user.hasAccess("CONTACT_TYPE_Y")))
    deleteContact();

"Then other issues start arising: what if a certain role destroys a Contact, that is linked to a File that the role does not have access to? What if a role has access to a subset of Files, and a subset of Contacts, but one of the Files the role has access to, has Contacts linked to it that the role does not have access to?"

To solve this you will need some extra custom logic before you allow a file or contact to be deleted. I suggest you think it through as pseudo-code before starting implementation.

function deleteFile($iFileId) {
    /* Check permissions */
    if (!user.hasAccess("FILE_DELETE")) return false;
    if (!user.hasAccess("FILE_TYPE_X")) return false;

    /* Prevent deletion of file if there are contacts linked /*
    $aContacts = (int)DB::GetValue(sprintf("SELECT COUNT(*) FROM business_contact WHERE file=%s;", 
                                   (int)$iFileId));
    if ($iContacts > 0) return false;

    /* Mark file as deleted and hide throughout application */
    DB::Exec(sprintf("UPDATE business_file SET del=1 WHERE id=%s;", (int)$iFileId));

    /* Mark linked contacts as removed links and hide throughout application */
    DB::Exec(sprintf("UPDATE business_file_contact SET del=1 WHERE file=%s;", (int)$iContactId));

}

function deleteContact(iContactId) {
    /* Check permissions */
    if (!user.hasAccess("CONTACT_DELETE")) return false;
    if (!user.hasAccess("CONTACT_TYPE_Y")) return false;

    /* Prevent deletion if linked to file */
    $iLinks = (int)DB::GetValue(sprintf(
        "SELECT COUNT(*) FROM business_file_contact WHERE contact=%s;", (int)$iContactId));
    if ($iLinks > 0) return false;

    /* Mark contact as deleted and hide throughout application */
    DB::Exec(sprintf("UPDATE business_contact SET del=1 WHERE id=%s;", (int)$iContactId));
}

"Is there an elegant way to solve this using the RBAC model? I've also looked into ARBAC, but it seems like overkill for a relatively simple application. To me, it seems that the root of all the issues is the many-to-many relationship between Files and Contacts. Are there specific ways to deal with a situation like this? I'm fairly new to this, and these permissions just landed in my lap for this project. I've looked into several Access Control models (even OrBAC), but none seem to be able to address this issue. Any help or hints in to the right direction would be greatly appreciated."

The above example will take you most of the way. It's quite normal to have many to many relationships in database structures, these are usually managed using a link table. Read up on the subject of Normalization and Entity Relationship Diagrams.

Should you need to add further controls regarding which records users have access to, then you could modify your hasAccess equivalent function to accept the record ID as a second parameter and apply whatever logic you need to in your hasAccess function, based on configured security policies in your database.

if (user.hasAccess("CONTACT_DELETE", 37991))
    deleteAccount();

If record access will be limited by default, then you could have a column on your business_contact or business_file tables that holds a whitelist of users with access. If the files will be in folders and sub-folders etc, then perhaps you would have this column on your folder record instead. An asterisk in this column could be interpreted as 'allow all'. Remember to at least include in this list the creator's user ID!

0

I know you wrote this a month ago and I am late to the battle. But what you describe has "attribute-based access control" written all over. helps you externalize / decouple authorization from the application / API you want to protect. This means you can develop functionality independently of the authorization logic.

There are a couple of standards out there - namely XACML and ALFA (abbreviated language for authorization).

This is what the architecture looks like:

XACML Architecture

  • the Policy Enforcement Point (PEP) intercepts the business flow and create an authorization request which it sends to the PDP
  • The Policy Decision Point (PDP) evaluates the incoming request against the policies it's been configured with. It eventually returns a decision to the PEP
  • The PDP may use Policy Information Points (PIP) to retrieve missing metadata (a user's department, role, location; a resource's department, owner...)

The answers that were previously given either require custom code or heavy role engineering or both. In ALFA, you don't need to do that. You simply write policies in plain old English that use the attributes you are interested in. For instance:

  • A user with the role == "clerk" can do action == "create" on object of type == "file"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.