Access Control

Kinvey allows an app to control access to its data through settings at both the collection and entity level. These permission settings establish a hierarchy whereby collection-level permissions control who can access the collection as a whole, and depending on these permissions, may allow individual entities to control access in a more fine-grained manner. This hierarchy offers useful high-level controls and robust lower level tuning options.

Collection Permissions

Collection-level permissions control users' overall access to the collection. Depending on the permission type, these can be overridden on a per-entity basis using entity permissions.

Collection permission are configured using roles and are accessible by visiting the Settings page of any collection using the Console. As an app developer, you decide which operations each role can perform and for each of these operations, what type of access users with this role will have.

Using the master secret, you can always access all data in your app, ignoring collection-level permissions.

Note that users cannot access a collection unless they have been granted a role that can access it. This means that if a collection has an empty permissions table, no one can access it. The All users role, which exists by default in every app and is automatically granted to all app users, is useful in controlling access for all users of your app without explicitly creating specific roles.

By default, each new collection is created using a set of permissions that allows all users of the app to create and read entities, but to only modify and delete entities they themselves have created.

This default set of permissions is equivalent to the Shared permission level offered in the past.

All supported operation and access types are described below, followed by examples of how you can use them in various combinations to control collection access and what each would mean for app users.

Operations

The available operations are:

CreateThe ability to create new entities within a collection. Note that only the Always and Never access types are applicable to this operation type, because the other two types (Grant and Entity) apply only to existing entities.
ReadThe ability to read existing entities within a collection.
UpdateThe ability to modify existing entities within a collection.
DeleteThe ability to delete existing entities within a collection.

Access types

The following table lists the available access types. When setting up role access, if an access type for a specific operation is not specified, members of the role do not receive any access privileges for that operation. This has the effect of implicitly disallowing the operation, unless another role grants more permissive privileges.

Any entity-level permissions that grant access to individual entities will be ignored if the user does not also have access through collection-level permissions.

NeverUsers will never be able to perform this operation, regardless of any other roles they may have as well as any entity-level permissions that might be in place.
AlwaysUsers will always be able to perform the operation, regardless of any entity-level permissions that might be in place.
GrantUsers will be able to perform the operation unless explicitly disallowed by entity-level permissions (using the global read/write ACL attributes).
EntityOnly those users who are granted access through entity-level permissions will be able to perform the operation.

Precedence works as follows if a user has more than one applicable role:

  • If the access type is Always, Grant, and Entity, the most permissive access type takes precedence.

    In other words, if the user is a member of three roles, one of which gives Always access to an operation and the other two give Grant and Entity, the user will have Always access, because this access type is the most permissive and will override the others.

  • Never always takes precedence over any other access type.

    In other words, if at least one of the user's roles enforces Never access to an operation, the user will be prohibited from executing it, regardless of what other permissive roles they are a member of.

A useful shorthand for talking about operations and access types is Operation:Access. For example, if a role can always create entities in a particular collection, but can never read them, we can write that the role has 'Create:Always, Read:Never' access to the collection.

Previously-offered permission types

You can model all permission levels offered before the introduction of roles using the new approach. We have automatically converted permission levels for your existing collections to their equivalents in the new roles model.

To understand the conversion better, examine how permission levels map to role-based permission models using the All users role:

SharedCreate:Always, Read:Grant, Update:Entity, Delete:Entity
PrivateCreate:Always, Read:Entity, Update:Entity, Delete:Entity
Read OnlyRead:Grant
FullCreate:Always, Read:Grant, Update:Grant, Delete:Grant

Example: Billing Statements

As an example, let's imagine you are creating an app that involves billing customers and issuing billing statements. To control access, you created three roles: BillingDept, Intern, and Customer. You have a couple of employees:

  • Alice is a full-time billing department employee. You have assigned her the BillingDept role.
  • John is an intern in the billing department. He has been assigned the BillingDept role, but also the Intern role.

Finally, you also have a customer named Bob. Bob has been assigned the Customer role.

To store the billing statements for each customer, you create a collection called BillingStatements. You would then want to assign each role the appropriate permission types, so that the billing statements are secure, while allowing each of the people described above the appropriate type of access. The permission table for this collection might look like this:

RoleCreateReadUpdateDelete
BillingDeptAlwaysAlwaysAlwaysAlways
InternNeverNever
CustomerEntity 

Let's break down what the above permission table means:

  • Alice (the full-time billing person) can create new billing statements, as well as read, update, and delete any entity in the collection. Because she has the Always access type for all of these operations, individual entities cannot override her access—Alice will always be able to perform any operation to any entity in this collection.

  • John (the billing intern) shares some of the billing responsibilities, but is not trusted to the same degree—he can update and read existing billing statements, but cannot create new ones or delete any statements. Since John has both the BillingDept and the Intern roles, he would by default be granted full access through his BillingDept Role. To prevent this, we use the Never access type (which, unlike the others, always takes precedence) in order to "make an exception" where interns are concerned, and override the permissions for the Create and Delete operations. Because we use the Never access type, any user with the Intern role cannot create or delete billing statements, regardless of what other roles that user may have.

  • Bob is a member only of the Customer role which normally denies him read access over the entities in the collection, as well as the ability to create new statements. However, he can read any entity that has specifically been set to grant him access—presumably, his own billing statements. He will only be able to read individual entities if those entities have explicitly used entity-level permissions to grant him read access. Because he does not have any Update or Delete permissions, he will not have access to delete or update any entities, even if some entities in this collection granted him explicit update or delete permissions.

Because only the three roles described above are part of the collection's permission table, any users who do not have any of these roles will not be able to access this collection at all.

Example: User Profiles

For another example, imagine you are creating a different app that involves letting your users create profiles that others can see. By default, a profile should be public. However, you may want to allow people to optionally make their own profile private, in which case they will need to explicitly grant access to their "friends", in order for those friends to be able to see the private profile. You also want to allow your technical support employees the ability to see and update all profiles, in order to troubleshoot and solve customer issues. To support these use cases, you create a role called TechSupport.

You create a Profiles collection to store user profiles and configure its permissions as follows:

RoleCreateReadUpdateDelete
All usersAlwaysGrantEntityEntity
TechSupportAlwaysAlways

As before, let's look at what these permissions mean:

  • All users of your app, regardless of any roles they may have (or whether they have a role at all), can create profiles. They can also update and delete the profiles they've created (or any other profile to which they have been given explicit permissions). Lastly, they can, by default, read any profile. However, the Read:Grant permission means that if a user sets their profile to "private" (by setting _acl.gr (or "global read") to false), then that user's profile will no longer be visible to other users, except to those who were given explicit entity-level permissions.

  • Tech support personnel (any user with the TechSupport role) can do all of the above, since they are also users of your app and are thus automatically granted the All users role. However, in addition, they can also read or update any user profile, regardless of whether it is public or private—this is because they have Read:Always and Update:Always permissions.

Common settings

When creating a new collection, many developers can leave its permissions unchanged. By default, all data within a collection is readable by any user, but writable only by the user who created the entity. This setup is suitable for many apps as it automatically protects against unauthorized modifications, while keeping the data open.

If the use case requires more privacy, you can use a combination of collection-level and entity-level permissions in your app to restrict access to its data.

Entity and User permissions

An individual entity can provide more specific permissions that control access only to itself rather than to the entire collection. It does so by setting various properties in its Access Control List (ACL), which is persisted as the entity's _acl field.

Depending on the collection-level permission settings, some roles may completely ignore these entity-level permissions: if a role is granted Always access, users with that role will always be able to access the entity. If a role is granted Never access, users with that role will never be able to access the entity. However, roles with the Grant access type will be denied access to any entities that set global read (for the Read operation) or global write (for the Update or Delete operations) to false. Roles with the Entity access type will be allowed access entirely though entity-level permissions.

When creating a new entity, its _acl.creator ACL field is set to the _id of the authenticated user who submitted the request. If an entity is created using the master secret, then its _acl.creator field will be set to the app key. Once an entity has been created, its creator field can never be changed (except using the master secret). Entity creators have read and write access as long as they belong to a role that has Always, Grant, or Entity access, and (aside from the master secret) are the only users who are allowed to change the _acl structure.

import Kinvey

/// Event.swift - an entity in the 'Events' collection
class Event : Entity, Codable {

    var name: String?
    var date: Date?
    var location: String?

    override class func collectionName() -> String {
        return "Event"
    }

    @available(*, deprecated, message: "Please use Swift.Codable instead")
    override func propertyMapping(_ map: Map) {
        super.propertyMapping(map)

        name <- ("name", map["name"])
        date <- ("date", map["date"], KinveyDateTransform())
        location <- ("location", map["location"])
    }
}

Global Access Control

You can make an entity visible and/or writable to all users of an app using globalRead and globalWrite on the Acl property and then saving the object.

event.acl?.globalRead.value = true // anyone can read
event.acl?.globalWrite.value = true // anyone can modify

Reader/Writer Lists

You can add other users by id to a list of readers or writers. Users with matching ids will have access that other users will not.

if let user = Kinvey.sharedClient.activeUser {
    let userQuery = UserQuery() {
        $0.email = "<#my-friends@email.com#>"
    }
    user.lookup(userQuery) {
        switch $0 {
        case .success(let users):
            print(users)
            if let myFriend = users.first {
                event.acl?.readers?.append(myFriend.userId)

                //add 'myFriend' to the writers list as well
                event.acl?.writers?.append(myFriend.userId)
                //... and then save the event
            }
        case .failure(let error):
            print(error)
        }
    }
}

Caveats

Write implies delete

With the exception of role-based permissions, if a user has write access to an object, they can delete it. They cannot, however, set permissions themselves. Only the creator can give and take permissions for the respective entity.

Role-based permissions are more specific and grant separate access for update vs. delete operations.

Write does not imply read

Having write access does not imply read access. To grant both kinds of access, simply add the user/group/role to both the reader and writer lists.

Master secret is "root"

The master secret has read and write access to all data and can modify any permissions setting at any level. Entity creators have no mechanism to take that access away.

Never embed the master secret in the app.

To import data from a legacy data source, use the master secret and set the ACL in a manner that preserves data ownership and access levels.

Use cases

If you have collections that only hold entities that the app developer or administrator can create or modify, such as a daily deal or a blog post, you may want to set collection-level permissions such that the default All Users role has Read:Grant access and no other permissions. This allows read access to any user of the app and write access only to the app developer using the master secret.

If you have some kind of watch list functionality where app users express interest in certain items by adding them to their watch list, and you store those in a separate collection, you might want to set collection-permissions such that the All Users role has Create:Always, Read:Entity, Update:Entity, Delete:Entity access. This means that any app user can create their own watch list and modify it, but not read another user's watch list. If you want watch lists to be visible to other app users by default, you could change the above such that the role has Read:Grant access instead.

The user collection is also controlled by the permissions described above. To enable a social app, you can make use of the fine-grained permissions described above. For example, set permissions for the user collection such that all users have Read:Entity permissions and let each user optionally make their profile public through the "global read" property. Another option is for users to open profile access only to their friends through the "readers" property. Note that the lookup method will always allow an app user to discover other users.