Files & Folders

Relationships between resources (encoded via the relations keyword in a resource block) commonly express roles and permissions that cascade down a hierarchy, such as files nested in folders in a filesystem.

Implement the logic

In this example, repositories contain folders, and folders contain nested folders and files. Roles cascade down the folder hierarchies from parent folder to children. This rule works recursively: a user has a role on a folder if they have that role on the parent folder. And the user has a role on the parent folder if they have a role on the parent folder's parent.


actor User { }
resource Repository {
roles = ["reader", "maintainer"];
}
resource Folder {
roles = ["reader", "writer"];
relations = {
repository: Repository,
folder: Folder,
};
"reader" if "reader" on "repository";
"writer" if "maintainer" on "repository";
role if role on "folder";
}
resource File {
permissions = ["read", "write"];
roles = ["reader", "writer"];
relations = {
folder: Folder,
};
role if role on "folder";
"read" if "reader";
"write" if "writer";
}

Test it works

Given the repository structure:


acme/anvil-py
├── python
│ ├── tests
│ │ ├── test.py
│ │ └── ...
│ └── ...
├── ...

We represent the relationships between the Anvil repository and its nested files and folders with has_relation facts.

And then check that a repository reader can read the test.py file.


test "folder roles apply to files" {
setup {
has_role(User{"alice"}, "reader", Repository{"anvil"});
has_relation(Folder{"python"}, "repository", Repository{"anvil"});
has_relation(Folder{"tests"}, "folder", Folder{"python"});
has_relation(File{"test.py"}, "folder", Folder{"tests"});
}
assert allow(User{"alice"}, "read", File{"test.py"});
}