Impersonation
Users can impersonate other users, which means they inherit some subset of the permissions that the other user has.
This pattern is useful in any system where some users need to troubleshoot or oversee other users. For example: customer support, HR, and payroll systems.
Implement the logic
In our example, customer support reps can get read-only access to all organizations a user belongs to.
The two main components are:
- A fact that indicates whether a user is impersonating another user.
- A policy that grants permissions to users who are impersonating another user.
actor User {  permissions = ["impersonate"];  "impersonate" if global "support";}global {  roles = ["support"];}resource Organization {  roles = ["admin", "member"];  permissions = ["read", "write"];  "member" if "admin";  "read" if "member";  "write" if "admin";}# a user can do anything some other user can do# if they are allowed to impersonate that user and# are currently impersonating themallow(user: User, action: String, resource: Resource) if  other_user matches User and  has_permission(user, "impersonate", other_user) and  is_impersonating(user, other_user) and  has_permission(other_user, action, resource);# we need to specify the default allow rule here# because we added our own custom one aboveallow(user: User, action: String, resource: Resource) if  has_permission(user, action, resource);
Test it out
In our test case, we'll put all the pieces together:
- Alice is a customer support rep and is impersonating Bob.
- Bob is an admin of the acme organization.
- And so Alice can see anything Bob can.
test "global support users can read user organizations via impersonation" {  setup {    has_role(User{"alice"}, "support");    has_role(User{"bob"}, "admin", Organization{"acme"});    has_role(User{"charlie"}, "member", Organization{"bar"});    is_impersonating(User{"alice"}, User{"bob"});  }  # bob can read as a member  assert allow(User{"bob"}, "read", Organization{"acme"});  # alice can impersonate bob  assert allow(User{"alice"}, "impersonate", User{"bob"});  # alice can read via Bob by impersonating bob  assert allow(User{"alice"}, "read", Organization{"acme"});  # charlie can read as a member  assert allow(User{"charlie"}, "read", Organization{"bar"});  # alice cannot read because alice is not impersonating charlie  assert_not allow(User{"alice"}, "read", Organization{"bar"});}
Extension
There may be several ways to define who has permission to impersonate another user. Common ones include:
From a relationship with the user:
actor User {  permissions = ["impersonate"];  relations = { manager: User };  "impersonate" if "manager";}
Or from a role on the organization:
actor User {  permissions = ["impersonate"];}resource Organization {  roles = ["member", "admin"];}# Organization admins can impersonate membershas_permission(user: User, "impersonate", other_user: User) if  org matches Organization and  has_role(user, "admin", org) and  has_role(other_user, "member", org);
The is_impersonating fact can be included as an ephemeral context
fact or more durably synced to Oso
Cloud. In the former case, when a user "ends" an
impersonation session, the application stops sending the impersonation context
fact. In the latter case, you need to delete the persisted fact.
Prefer context facts when you want impersonation restricted to a single application or service and persisted facts when you want the impersonation session to be shared across services.