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.
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.
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:
Create | The 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. | |
Read | The ability to read existing entities within a collection. | |
Update | The ability to modify existing entities within a collection. | |
Delete | The 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.
Never | Users 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. | |
Always | Users will always be able to perform the operation, regardless of any entity-level permissions that might be in place. | |
Grant | Users will be able to perform the operation unless explicitly disallowed by entity-level permissions (using the global read/write ACL attributes). | |
Entity | Only those users who are granted access through entity-level permissions will be able to perform the operation. |
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:
Shared | Create:Always, Read:Grant, Update:Entity, Delete:Entity |
Private | Create:Always, Read:Entity, Update:Entity, Delete:Entity |
Read Only | Read:Grant |
Full | Create: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 theIntern
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:
Role | Create | Read | Update | Delete |
---|---|---|---|---|
BillingDept | Always | Always | Always | Always |
Intern | Never | Never | ||
Customer | Entity |
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 theIntern
roles, he would by default be granted full access through hisBillingDept
Role. To prevent this, we use theNever
access type (which, unlike the others, always takes precedence) in order to "make an exception" where interns are concerned, and override the permissions for theCreate
andDelete
operations. Because we use theNever
access type, any user with theIntern
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 anyUpdate
orDelete
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:
Role | Create | Read | Update | Delete |
---|---|---|---|---|
All users | Always | Grant | Entity | Entity |
TechSupport | Always | Always |
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") tofalse
), 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 theAll 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 haveRead:Always
andUpdate: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.
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.