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.