Welcome back to Habitat's road to release! Today, we're making available our API for playing with fine-grained permission-ing on top of spaces, using relationship-based access control. Buckle up, because this is a long one! And as a reminder, more documentation on everything covered can be found here, and join us on Discord to ask questions directly!
Why we're building this
Fine-grained permissioning is necessary to support the rich interactions of modern-day software. For example, a collaborative document editor or collaborative canvas may distinguish between an owner / editor / commentor / viewer, each role with varying degrees of what they can do within the app. We'd like to build these types of interactions on top of spaces in order to maintain data portability and interoperability within such apps.
Example share modal of expected rich-permissions within apps.
Additionally, Habitat's goal is to give organizations control over their own data and context. This includes self-knowledge about the organization that is typically lost to third-parties and service providers which do not interoperate on a shared understanding of organizational identities, roles, hierarchies, and permissions, nor do they typically allow organizations to set how these are used within an app. (e.g. I can't easily make a "group" within my company and tell both Slack and Notion to make that group the source of truth for a channel and document access. And what if another organization wants to share something with that "group" in my org...). But, we have the opportunity to do exactly this in the ATmosphere!
Example of what one might want to do
This is a very different type way to use spaces as compared to the work around communities: while that spec is suited for creating a boundary around people in an open social network that has relatively open flow within the boundary, we are working at a lower level. We want to create boundaries around artifacts and metadata for collaboration tools. At the end of the day, it's all reads + writes of data and governing who gets to do that, but the APIs we need are quite different. In this way, our API can be thought of as yet another "peer" to the simplespace and opensocial implementations for interacting with spaces. As we formalize this work, we will release a spec so other space hosts can implement it as well and interoperate with each other, but today it exists as an API that Habitat supports as a space host.
There's well understood and supported work around doing ReBAC and fine-grained permissioning for applications, such as OpenFGA and SpiceDB. These systems have been developed for efficiently running queries on complex permission systems, so we'd like to reuse them.
What we're building
We are introducing a new set of lexicons under the namespace network.habitat.relationship.*. These lexicons introduce the concept of relationships between users --> spaces and also spaces --> other spaces, which can be used to support inheritance between spaces and build fine-grained permissions on top of spaces. This new API can be used in conjunction with the pre-implemented simplespace API on Habitat to start experimenting with finer-grained permissioning right away.
Our documentation goes deeper into how this works, but for the purposes of the blogpost, I'll walk through a motivating example and how it could be implemented: a document editor. Let's take the above owner / editor / commentor / viewer roles. We want each to imply the next (i.e. owner can do whatever anything below it can do) and also be distinct (you can't have both the owner and a viewer role, you are one or the other). We also want whatever system we come up with to support this to extend to other such cases and be generalizable.
Our key observation that we are going to use to build this whole system is that you can treat permissioned spaces as a user-set, or a named reference that represents a set of users. This makes management of hierarchical and inheritance structures much simpler.
For a bit of background: in relationship-based access control, there are three terms to understand: subjects, relations, and objects. This breaks down into "who" can "have some kind of relationship" to "what". In our case, the "what" will always be a space (represented by a URI), and the "who" can be either a DID or a space-relations (more on this below). We are opinionated about what the kinds of relationships can be: we name owner, manager, writer, and reader as the four relations a subject can have to a space. (Green denotes relations to a space through our API, Yellow is the role in the example app I talk through below).
Note that the relationships a subject has to a space is lower-level than the owner / editor / commentor / viewer roles we are trying to support in the document editor; those govern how users of an application can interact with a document, while our API governs how DIDs + spaces interact with other spaces.
Okay, so let's try to model these document roles using ReBac:
viewercan read document content + commentscommentorcan read document content + write commentseditorcan write document content + write commentsownercan do all of that and manage who can edit / comment / view
Stacking this on top of user-relations and space-relations, we can define two spaces for a document, example.space.type.docEdits and example.space.type.docComments. Then we can do the following:
Making someone a
viewermeans granting themreaderrole on bothdocEditsanddocCommentsspaces.Making someone a
commentormeans granting them areaderrole ondocEditsand awriterrole ondocComments.Making someone an
editormeans giving them awriterrole on bothdocEditsanddocComments.Making someone a document
ownermeans giving them theownerrole on both spaces.
(We don't use the manager role in this example, but for e.g. it could be used to create "admins" of a doc who can add other editors and viewers -- these decisions are left up to the app developer)
What's interesting about this is that though these relationships are defined in the context of one document, they can be reused outside of that. For example, say your app supports side-docs (shoutout Leaflet) where now viewers and commentors of the original doc can edit the side-doc. Without having to enumerate all the DIDs that were viewers and commentors of the main-doc, this can be supported by doing the following:
Create a new
example.space.type.sideDocspace.Create the following relationships:
ownerondocEditshas relationshipownertosideDocreaderondocEditshas relationshipwritertosideDoc(through inheritance, this implieswriterondocEditsalso has relationshipwritertosideDoc)
Now, through inheritance, adding someone as a viewer through an app UI to a doc also gives them access to edit the side doc! And going back to the original four roles, we see that we can actually derive the relationships on both sideDoc and docComments from the docEdits root space. So for example, rather than setting a user-relation on both docComments and docEdits for the owner of the doc, we can set a user-relation for the owner on the docEdits space, then use a space-relation to say the owner of the docEdits space is also an owner of the docComments space and so on for editor. Even in this simple case, there's a bunch of different ways the relationships can be structured (and probably requires a bit more thought to do correctly), but that's part of the beauty of being able to layer spaces like this: it's up to the application developer to decide what is best for their given situation.
How is this different than the Arbiter?
From Zicklag's most recent post, the arbiter is more focused on, well, arbiting reads and writes to a shared community or org PDS. In contrast, this API is focused on governing reads and writes to shared spaces granularly. In the coming weeks, this point will become more clear as we release more about how organizations are structured in Habitat. We also decided to do opinionated relationship-based access control rather than policy governance or some other system because it felt like the right layer for a space host to operate at and simple enough that there's a path open for interoperability, and (hopefully) leave more complex application-level concerns up to the developer.
App interoperability + portability
All relationships defined through this system are written into the space as a record owned by the space owner. We chose to do it this way for a couple of reasons:
- 1.
The sync protocol for permissioned spaces is pretty neat and simple, and we'd like apps to be able to rely on that protocol to sync permission data, rather than creating a new protocol. Writing the relationships as records means we maintain the same level of app interoperability that there is with any other record in a space.
- 2.
Going back to Habitat's mission, we are trying to give organizations portability across the apps and services they use. That means when they stop using an app, they should have credible exit: they should be able to take the most important context and data from that app to another service, or at least be able to hold onto that. Permissions and data governance are critical to any organizational operations, and permissions of app data can be viewed as part of this data itself. As any cloud-to-cloud migration service knows, the most difficult piece is migrating permissions and without this, the migration is unusable. So, we want to put this data directly into the hands of organizations.
Open questions
This API is a work-in-progress and will continue to evolve!!! We landed upon it after iterating on approaches with motivating use cases that we personally will need to implement, but have by no means enumerated every such use case for this API. One major open question we have is around space host interoperability: if two space hosts both implement this API, how might they interoperate to support relationships like "readers of space A on space host A now has access to space B on space host B"? More thoughts on this soon.
There's also a lot that we left out of this blogpost, such as how we do authz for writing these relationship records themselves so that they can't be set arbitrarily by anyone. This is all covered in our documentation, and as always, join our Discord to continue the conversation.
What's next
Lots of polishing! We want people to be able to explore the permission-ed spaces and relations they are creating with these APIs. We're building up to more organizational primitives with these APIs as well.
Next week, we'll be releasing š«¢ not an API, but something we've built on top of these APIs that non-developers might be interested in too :) If you've been taking notes, you might be able to guess what's coming š
-Habitat team, talk š
P.S. is it commentor or commenter ? this is no longer a real word