Storing Data & Schema
Permify unifies your authorization data and the authorization schemas you have in a database of your preference, which serves as the single source of truth for all authorization queries and requests via the Permify API.
In Permify, you can store authorization data in two different forms: as relationships and as attributes.
Let’s examine relationships first.
Relationships
In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs).
These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. Each relational tuple represents an action that a specific user or user set can do on a resource and takes form of user U has relation R to object O
, where user U could be a simple user or a user set such as team X members.
In Permify, the simplest form of relational tuple structured as: entity # relation @ user
. Here are some relational tuples with semantics,
Attributes
Besides creating and storing your authorization-related data as relationships, you can also create attributes along with your resources and users.
For certain use cases, using relationships (ReBAC) or roles (RBAC) might not be the best fit. For example, geo-based permissions where access is granted only if associated with a geographical or regional attribute. Or consider time-based permissions, restricting certain actions to office hours. A simpler scenario involves defining certain individuals as banned, filtering them out from access despite meeting other requirements.
Attribute-Based Access Control takes a more contextual approach, allowing you to define access rights based on the context around subjects and objects in an application.
In Permify, the form of attributes are similar to relational tuples but with a small syntax differentiation:
subject $ attribute | value
Here are some attributes with semantics,
account:1$balance|double:4000
- account:1’s balance is defined as 4000.post:546$is_restricted|boolean:true
- post:546 is labeled as restricted post within the system.user:122$regions|string[]:US,MEX
- user:122 is associated with regions United States and Mexico.
Where is the stored Authorization Data used?
These relational tuples and attributes represents your authorization data.
Permify stores your these data in a database you prefer. You can configure the database when running Permify Service with using configuration options.
Stored data are queried and utilized in Permify APIs, including the check API, which is an access control check request used to determine whether a user’s action is authorized.
As an example; to decide whether a user could view a protected resource, Permify looks up the relations between that specific user and the protected resource. These relation types could be ownership, parent-child relation, a role such as an admin or manager or even an attribute.
Creating Authorization Data
Relationships and attributes can be created with an simple API call, Since these attributes and relations are live instances, meaning they can be affected by specific user actions within the application, they should be created/deleted with a simple Permify API call at runtime.
Each relational tuple or attribute should be created according to its authorization model, Permify Schema.
Let’s follow a simple document management system example with the following Permify Schema to see how to create relation tuples.
entity user {}
entity organization {
relation admin @user
relation member @user
}
entity document {
relation owner @user
relation parent @organization
relation maintainer @user @organization#member
action view = owner or parent.member or maintainer or parent.admin
action edit = owner or maintainer or parent.admin
action delete = owner or parent.admin
}
According to the schema above; when a user creates a document in an organization, more specifically let’s say, when user:1 create a document:2 we need to create the following relational tuple,
document:2#owner@user:1
Write Data API
You can create relational tuples by using Write Data API
.
rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest {
TenantId: "t1",
Metadata: &v1.DataWriteRequestMetadata {
SchemaVersion: ""
},
Tuples: [] * v1.Tuple {
{
Entity: & v1.Entity {
Type: "document",
Id: "2",
},
Relation: "owner",
Subject: & v1.Subject {
Type: "user",
Id: "1",
},
}
},
})
Snap Tokens
In Write Data API response you’ll get a snap token of the operation.
{
"snap_token": "FxHhb4CrLBc="
}
This token consists of an encoded timestamp, which is used to ensure fresh results in access control checks. We’re suggesting to use snap tokens in production to prevent data inconsistency and optimize the performance. See more on Snap Tokens
More Examples
Let’s create more example data according to the schema we defined above.
Organization Admin
relational tuple: organization:1#admin@user:3
Semantics: User 3 is administrator in organization 1.
rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest {
TenantId: "t1",
Metadata: &v1.DataWriteRequestMetadata {
SchemaVersion: ""
},
Tuples: [] * v1.Tuple {
{
Entity: & v1.Entity {
Type: "organization",
Id: "1",
},
Relation: "admin",
Subject: & v1.Subject {
Type: "user",
Id: "3",
},
}
},
})
Parent Organization
Relational Tuple: document:1#parent@organization:1#…
Semantics: Organization 1 is parent of document 1.
rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest {
TenantId: "t1",
Metadata: &v1.DataWriteRequestMetadata {
SchemaVersion: ""
},
Tuples: [] * v1.Tuple {
{
Entity: & v1.Entity {
Type: "document",
Id: "1",
},
Relation: "parent",
Subject: & v1.Subject {
Type: "organization",
Id: "1",
Relation: "..."
},
}
},
})
Note: relation: “...”
used when subject type is different from user entity. #… represents a relation that does not affect the semantics of the tuple.
Simply, the usage of … is straightforward: if you’re use user entity as an subject, you should not be using the ...
If you’re using another subject rather than user entity then you need to use the ...
Organization Members Are Maintainers in specific Doc
Created relational tuple: document:1#maintainer@organization:2#member
Definition: Members of organization 2 are maintainers in document 1.
rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest {
TenantId: "t1",
Metadata: &v1.DataWriteRequestMetadata {
SchemaVersion: ""
},
Tuples: [] * v1.Tuple {
{
Entity: & v1.Entity {
Type: "document",
Id: "1",
},
Relation: "maintainer",
Subject: & v1.Subject {
Type: "organization",
Id: "2",
Relation: "member"
},
}
},
})
Test this Example on Playground
Audit Logs For Permission Changes
Permify does support audit logs for permission changes. Leveraging the MVCC (Multi-Version Concurrency Control) pattern, we maintain a history of all permission data changes. This essentially provides an audit trail, allowing users to track alterations and when they occurred.
In cloud version, our system supports change history auditing. It automatically generates and securely stores logs for all significant actions. These logs detail who made the change, what was changed, and when the change occurred. Furthermore, your system allows for easy searching and analysis of these logs, supporting automated alerting for suspicious activities. This comprehensive approach ensures thorough and effective auditing of all changes
Permission Baselining (Reviewing)
We have a strong foundation for permission baselining and review, thanks to MVCC.
Historical Review: You can review the history of permissions changes as each version is stored. This enables retrospective audits and analysis.
Current State Review: You can review the current state of permissions by examining the latest versions of each permission setting.
Cleanup: Your system incorporates a garbage collector for managing old data, which helps keep your permissions structure clean and optimized.