Entitlements
In applications with differentiated subscription tiers or paid features users' permissions are determined by the features they have paid for.
Implement the logic
The core part of the entitlements logic is to conditionally grant a user permission based on a combination of their role and attributes of the organization (like the subscription tier they are on).
For example, we can grant users permission to create repositories for an organization if they have the "member" role for that organization and the organization has quota remaining for creating repositories.
actor User { }resource Organization { roles = ["admin", "member"]; permissions = ["repository.create"]; "member" if "admin";}resource Plan { roles = ["subscriber"]; relations = { subscribed_organization: Organization }; "subscriber" if role on "subscribed_organization";}resource Feature { relations = { plan: Plan };}has_permission(user: User, "repository.create", org: Organization) if has_role(user, "member", org) and has_quota_remaining(org, Feature{"repository"});has_quota_remaining(org: Organization, feature: Feature) if quota matches Integer and has_quota(org, feature, quota) and used matches Integer and quota_used(org, feature, used) and used < quota;has_quota(org: Organization, feature: Feature, quota: Integer) if plan matches Plan and has_relation(plan, "subscribed", org) and plan_quota(plan, feature, quota);
Test the logic
To test this we have:
- A few organizations with different plans and quotas used.
- Users belonging to each of those organizations.
- Tests to check which users can still create repositories.
declare plan_quota(Plan, Feature, Integer);declare quota_used(Organization, Feature, Integer);plan_quota(Plan{"pro"}, Feature{"repository"}, 10);plan_quota(Plan{"basic"}, Feature{"repository"}, 0);test "members can create repositorys if they have quota" { setup { quota_used(Organization{"apple"}, Feature{"repository"}, 5); quota_used(Organization{"netflix"}, Feature{"repository"}, 10); quota_used(Organization{"amazon"}, Feature{"repository"}, 0); has_relation(Plan{"pro"}, "subscribed", Organization{"apple"}); has_relation(Plan{"pro"}, "subscribed", Organization{"netflix"}); has_relation(Plan{"basic"}, "subscribed", Organization{"amazon"}); has_role(User{"alice"}, "member", Organization{"apple"}); has_role(User{"bob"}, "member", Organization{"netflix"}); has_role(User{"charlie"}, "member", Organization{"amazon"}); } assert has_quota_remaining(Organization{"apple"}, Feature{"repository"}); # Apple has quota remaining, so all good assert allow(User{"alice"}, "repository.create", Organization{"apple"}); # Netflix has used all quota assert_not allow(User{"bob"}, "repository.create", Organization{"netflix"}); # Amazon doesn't have any quota left assert_not allow(User{"charlie"}, "repository.create", Organization{"amazon"});}