Use Oso to Help Your Customers Share Anything: Documents, Drawings, Data

Google Docs introduced many people to resource-specific roles––even if users never thought about it in those terms. This paradigm enables seemingly simple statements like "Alice is an editor on this document." Behind the scenes, this means Alice has a role ("editor") on a specific resource (the document). To provide this feature, your app needs tremendous flexibility in managing users, resources, and roles. As users become more sophisticated, they've come to expect resource-specific roles as table stakes for many types of applications.

While Google Docs and Drive might be the most familiar examples, many other applications rely on resource-specific roles for collaboration:

  • Figma and other design tools let users share and collaborate on design files and components
  • Code repositories like GitHub offer complex options to manage codebases, with both roles and individual permissions
  • Kickstarter enables project collaboration, delegating permissions between project management, community management, and order fulfillment

Oso makes managing each of these resource sharing scenarios simple, flexible, and extensible.

Building Resource-Specific Roles with Polar

Oso's Polar language lets you express sharing relationships clearly and concisely. Here's a basic example of modeling resource-specific roles:

actor User { }

resource Resource {
	permissions = ["view", "edit", "manage_share"];
	roles = ["viewer", "editor", "admin"];

	"viewer" if "admin";
	"editor" if "admin";

	"view" if "viewer";
	"edit" if "editor";
	"manage_share" if "admin";
}

This foundation makes it easy to define which users have which roles on which resources. We can demonstrate this using Polar's inline unit tests:

test "users with the reader role can read resources" {
    setup {
        has_role(User{"alice"}, "reader", Resource{"anvil"});
        has_role(User{"bob"}, "reader", Resource{"anvil"});
    }

    assert allow(User{"alice"}, "read", Resource{"anvil"});
    assert allow(User{"bob"}, "read", Resource{"anvil"});
    # Bob does not have a role that can write
    assert_not allow(User{"bob"}, "write", Resource{"anvil"});
}

Adding Group Sharing

This foundation is remarkably flexible. Need to add  sharing for groups or teams? Just introduce Groups as a type of user and add another rule:

actor User { }

# A group is a kind of actor
actor Group { }

# Use the same Resource definition from above
resource Resource {...}

# Users inherit roles from groups
has_role(user: User, role: String, resource: Resource) if
	group matches Group and
	has_group(user, group) and
	has_role(group, role, resource);

test "group members can read resources" {
	setup {
		# Groups
		has_role(Group{"smiths"}, "reader", Resource{"anvil"});
		has_group(User{"alice"}, Group{"smiths"});
		has_group(User{"bob"}, Group{"smiths"});

		# Resource-specific roles
		has_role(User{"bob"}, "writer", Resource{"anvil"})
	}

	assert allow(User{"alice"}, "read", Resource{"anvil"});
	assert allow(User{"bob"}, "read", Resource{"anvil"});
	# Bob has an explicit writer role on Anvil
	assert allow(User{"bob"}, "write", Resource{"anvil"});
}

This lets you easily share the same permissions with all members of a team at once. And what's great is that the flexibility means that you can also assign individual members of the teams a disjoint set of permissions without a problem.

Multi-tenancy with Sharing

In Oso, we refer to "tenants" as Organizations. You can easily add organization support to this resource-sharing foundation using our relationship-based access shorthand:

actor User { }

resource Organization {
	roles = ["member", "admin"];
	permissions = ["read", "create_user"];

	"member" if "admin";
	"read" if "member";
	"create_user" if "admin";
}

resource Resource {
	permissions = ["view", "edit", "manage_share", "have_role"];
	roles = ["viewer", "editor", "admin"];
	relations = {
		belongs_to: Organization
	};

	"have_role" if "member" on "belongs_to";
	# same as above
	...
}

test "organization members can have resource roles" {
	setup {
		has_role(User{"alice"}, "admin", Organization{"acme"});
		has_relationship(Resource{"anvil"}, "belongs_to", Organization{"acme"});
	}

	assert allow(User{"alice"}, "have_role", Resource{"anvil"});
}

Why This Matters

The power of Oso's approach lies not just in writing these rules, but in maintaining and evolving them. As your application grows, you'll need to:

  • Add new types of resources
  • Support new sharing patterns
  • Handle edge cases and special permissions
  • Maintain consistency across your application

With Oso, these changes are localized to your authorization policy, not scattered throughout your application code. This separation of concerns makes your code more maintainable and easier to reason about.

See It in Action

If you're curious what a more complete example of building a resource sharing application with Oso looks like, check out our reference implementation of a file-sharing app on GitHub.

Ready to simplify your permissions management? Visit our docs and sign up for a free Oso developer account.

Want us to remind you?
We'll email you before the event with a friendly reminder.

Write your first policy