Model Resource Fields in Permissions
To implement field-level authorization, you can specify a permission per action per field on the "parent" resource. This approach can be straightforward in terms of your policy, but might require more complex client-side processing.
For alternative approaches and more context, see Field-Level Authorization.
Implementation overview
In this approach, you encode field-level authorization into permissions on the
"parent" resource. To make this permission's purpose clear, we recommend
demarcating the field and the permission with some character, such as a ..
resource Account {  permissions = [    # resource-level permissions    "read", "update",    # field-level permissions    "email.read", "email.update",  ];  "read" if "update";  "email.read" if "read";  "email.update" if "update";}
Another way to think about "fields in permissions" is that it lets you create "logical resources" by mentioning them in other resource's permissions. However, these logical resources are never explicitly referenced in your policy and only exist implicitly. This contrasts with the "fields as resources" approach which explicitly creates resources for fields.
A benefit of this approach is that you can get all of a user's permissions on a
resource with the
actions
subcommand, and then manipulate the return values to determine the fields they
can access.
However, this approach has a few downsides:
- Application code might need to manipulate data from Oso to make authorization decisions.
- Policies must provide explicit sets of fields, as well as the permissions you want to allow on them. This introduces a multiplicative number of permissions to manage.
Example
We'll model a social app with an Account whose fields we want to apply
granular access to––specifically two rules that are simpler to model using
field-level authorization.
| User role | Special case | 
|---|---|
| community_admin | Can update other Accounts' username fields, but no other fields. | 
| visitor | Can read others Accounts, but none of their fields. | 
Policy
To accomplish the conditions stated above, we'll include:
- 
Permissions per action per field: resource Account {permissions = [# resource-level permissions"read", "update",# field-level permissions"username.read", "username.update","email.read", "email.update",];
- 
RBAC per action per field: # username## username.update"username.update" if "owner";"username.update" if "admin" on "parent";"username.update" if "community_admin" on "parent";## username.read"username.read" if "username.update";"username.read" if "member" on "parent";
This policy shows all of the pieces working together.
actor User {}resource Organization {  roles = ["visitor", "member", "community_admin", "admin"];  permissions = ["read", "update"];  # Role implication  # visitor < member < community_admin < admin  "visitor" if "member";  "member" if "community_admin";  "community_admin" if "admin";  # RBAC  "update" if "admin";  "read" if "visitor";}resource Account {  permissions = [    # resource-level permissions    "read", "update",    # field-level permissions    "username.read", "username.update",    "email.read", "email.update",  ];  relations = { parent: Organization, owner: User };  # RBAC  # Resource-level permissions  #  #         relation          | read | update  # --------------------------|------|--------  # owner                     |   x  |    x  # admin on parent           |   x  |    x  # community_admin on parent |   x  |    x  # member on parent          |   x  |    -  # visitor on parent         |   x  |    -  "update" if "owner";  "update" if "admin" on "parent";  # "update" is a higher-level permission than "username.update", so apply it  # to community_admin.  "update" if "community_admin" on "parent";  "read" if "update";  "read" if "visitor" on "parent";  # Field-level permissions  #  #         relation          |   username   |     email  # --------------------------|--------------|----------------  # owner                     | read, update | read, update  # admin on parent           | read, update | read, update  # community_admin on parent | read, update | read  # member on parent          | read         | read  # visitor on parent         |       -      |       -  # username  ## username.update  "username.update" if "owner";  "username.update" if "admin" on "parent";  "username.update" if "community_admin" on "parent";  ## username.read  "username.read" if "username.update";  "username.read" if "member" on "parent";  # email  ## email.update  "email.update" if "owner";  "email.update" if "admin" on "parent";  ## username.read  "email.read" if "email.update";  "email.read" if "community_admin" on "parent";  "email.read" if "member" on "parent";}test "Fields in permissions" {  setup {    # admin    has_role(User{"alice"}, "admin", Organization{"example"});    has_relation(Account{"alice"}, "owner", User{"alice"});    has_relation(Account{"alice"}, "parent", Organization{"example"});    # community_admin    has_role(User{"bob"}, "community_admin", Organization{"example"});    has_relation(Account{"bob"}, "owner", User{"bob"});    has_relation(Account{"bob"}, "parent", Organization{"example"});    # member    has_role(User{"charlie"}, "member", Organization{"example"});    has_relation(Account{"charlie"}, "owner", User{"charlie"});    has_relation(Account{"charlie"}, "parent", Organization{"example"});    # visitor    has_role(User{"dana"}, "visitor", Organization{"example"});    has_relation(Account{"dana"}, "owner", User{"dana"});    has_relation(Account{"dana"}, "parent", Organization{"example"});  }  # anyone can update any field of their own account  assert    allow(User{"charlie"}, "update", Account{"charlie"});  assert    allow(User{"charlie"}, "username.update", Account{"charlie"});  assert    allow(User{"dana"}, "update", Account{"dana"});  assert    allow(User{"dana"}, "username.update", Account{"dana"});  # admins can update all fields in all accounts  assert	allow(User{"alice"}, "username.update", Account{"bob"});  assert	allow(User{"alice"}, "email.update", Account{"bob"});  assert	allow(User{"alice"}, "username.update", Account{"charlie"});  # community admins have resource-level update permissions  assert	allow(User{"bob"}, "update", Account{"alice"});  assert	allow(User{"bob"}, "update", Account{"dana"});  # community admins can only update usernames, but can read all fields  assert	allow(User{"bob"}, "username.update", Account{"alice"});  assert	allow(User{"bob"}, "username.update", Account{"charlie"});  assert_not	allow(User{"bob"}, "email.update", Account{"alice"});  assert_not	allow(User{"bob"}, "email.update", Account{"dana"});  assert	allow(User{"bob"}, "email.read", Account{"alice"});  # members have read access to all fields  assert	allow(User{"charlie"}, "username.read", Account{"alice"});  assert	allow(User{"charlie"}, "email.read", Account{"dana"});  assert_not	allow(User{"charlie"}, "email.update", Account{"dana"});  # visitors only have read access to others' accounts  assert	allow(User{"dana"}, "read", Account{"alice"});  assert	allow(User{"dana"}, "read", Account{"charlie"});  assert_not	allow(User{"dana"}, "update", Account{"charlie"});  # visitors have no field-level access  assert_not	allow(User{"dana"}, "username.read", Account{"alice"});  assert_not	allow(User{"dana"}, "email.read", Account{"charlie"});}
Client
The benefit of fields in permissions is that you can derive field-level
authorization using the actions subcommand.
To determine a community_admin's permissions on an account that is not their
own:
oso-cloud actions User:bob Account:alice
email.readreadupdateusername.readusername.update
With that output, manipulate the text to determine which fields the user can read:
oso-cloud actions User:bob Account:alice | awk -F '.' '/.*\.read/ {print $1}'
emailusername
Or update:
oso-cloud actions User:bob Account:alice | awk -F '.' '/.*\.update/ {print $1}'
username