28 May 2011

Permissions in Fluidinfo

Probably the hardest part of Fluidinfo to understand in detail is its permissions system. At a high level, it’s rather simple and wonderful: each user can can separately control read and write permissions for each tag (and namespace) under her username. Moreover, these are not binary controls, making them public or private, but allow the owner to allow or disallow any individual from reading, using or modifying any of her tags.

The permissions system is, in my view, one of the most powerful and important aspects of Fluidinfo, as I described in two previous posts—Permissions Worth Getting Excited About and The Permissions Sketch.

Unfortunately, understanding precisely how permissions work in Fluidinfo, and how you control them through the API, is quite hard. I think it is harder than it needs to be, largely because I think the interface to the permissions system presents the wrong abstract model of tags.

The purpose of this post is two-fold:

  1. I am going to try to give the clearest explanation yet of how the current permissions actually work and how you control them through the API. (The online documentation on them is complete and accurate but rather labyrinthine.)
  2. I am going to suggest a way in which it might be simplified, which will probably form the basis of new fdb commands whether the API changes or not.

I plan to add commands to fdb that provide a higher level interface to the permissions, together with a more granular interface based on the simplified scheme proposed below.

The High Level View of a Fluidinfo Permission

Every action that can be performed on a tag or a namespace in Fluidinfo is controlled by a permission. A permission permits or denies each user the ability to perform the associated action. The permission consists of a policy, which is either open or closed, and an exception list—a list of users for whom the policy is reversed.

By default, read actions have open policies with empty exception lists, meaning that anyone can read the data. Similarly, by default, write actions have closed policies with an exception list consisting only of the user in whose (top-level) namespace the tag exists.

So, for example, my rating tag, njr/rating has its read permissions set to:

njr/rating: read policy {open}, exceptions = []

njr/rating: write policies {closed}, exceptions = [njr]

So at the high level, this is all pretty simple. Now let’s dig a little deeper.

The Current Fluidinfo Permissions Structure in Detail

In reality, there is not merely a read permission and a write permission for each tag and namespace. Namespaces actually have three different write permissions, a read permission and a control permission.

Tags have even more. A tag has four separate write permissions, one read permission and two control permissions.

The control permission controls who can change the permissions on an object.

The full set of permissions is illustrated in the table below.

fi-perms-current-njr.png

The first columns shows the kind of permission (read, write, control), the second the name of the action Fluidinfo uses to describe that permission, the third column shows what the permission controls in the case of namespaces, and the last column shows what the permission controls in the case of tags.

A curious thing you will notice is that in the case of tags, there are two copies of the same permission, with the same name. This doesn’t lead to mayhem, because they are accessed through different paths.

An expanded version of the table is shown below, in which I’ve added in the path (relative to http://fluiddb.fluidinfo.com) used to control the permission in question. To read the current state of the permission from Fluidinfo, you perform an HTTP GET on the relevant path, and to set a permission’s state you perform an HTTP PUT on the same URL. In these urls, I have used NSPATH to indicate the full name of the namespace (which will includes slashes, if it is a subnamespace) and TAGPATH to indicate the full path of the tag, which should include the full namespace. For example, in the case of my rating tag, TAGPATH is njr/rating. If I had a private rating tag, its TAGPATH might be njr/private/rating.

fi-perms-paths-njr.png

Reading and Setting Permissions

To read a the permissions on a tag or namespace, you perform an HTTP get on the path given in the table above. Using curl, I could achieve this by saying:

curl -u njr:'password' 'http://fluiddb.fluidinfo.com/permissions/tag-values/njr/rating?action=read'

with the appropriate password, and if I do, the output I get is:

{"policy": "open", "exceptions": []}

This is slightly painful, so I’ll illustrate it using the fdb python library from now on.

This program:

from fdb import FluidDB
db = FluidDB()
print db.call('GET', '/permissions/tag-values/njr/rating', action='read')
print db.call('GET', '/permissions/tag-values/njr/rating', action='create')

produces this output:

(200, {u'policy': u'open', u'exceptions': []})
(200, {u'policy': u'closed', u'exceptions': [u'njr']})

(The 200’s are the HTTP response code OK.)

Setting permissions is similar. The program below illustrates setting the create permission for the tag njr/geotagged, first to its default value and then adding onigiri to the access list, allowing him to tag objects with this tag.

from fdb import FluidDB, json
db = FluidDB()
print db.call('PUT', '/permissions/tag-values/njr/geotagged',
              json.dumps({'policy': 'closed', 'exceptions': ['njr']}),
              action='create')
print db.call('GET', '/permissions/tag-values/njr/geotagged', action='create')
print db.call('PUT', '/permissions/tag-values/njr/geotagged',
              json.dumps({'policy': 'closed',
                          'exceptions': ['njr', 'onigiri']}),
              action='create')
print db.call('GET', '/permissions/tag-values/njr/geotagged', action='create')

When run, the output is:

(204, '')
(200, {u'policy': u'closed', u'exceptions': [u'njr']})
(204, '')
(200, {u'policy': u'closed', u'exceptions': [u'njr', u'onigiri']})

(The HTTP response code of 204 means no content, which is what Fluidinfo returns for successful PUT operations; if the operation failed, it would produce an error code, probably a 4xx.)

Why are the Permissions Like This?

The permissions API is like it is because of Fluidinfo’s “mental model” (if you will forgitve the anthropomorphisation) and internal structure.

The way I like to think about Fluidinfo is that it is entirely based on tags, that may or may not have values, and which can be collected together into hierarchical namespaces. If I want to rate something a 7, I simply slap an njr/rating tag onto its object with the value 7. If I want to collect a bunch of tags pertaining to books together, I might use tags like njr/book/summary and njr/book/lent-to. The interface provided by my fdb library uses exactly this philosophy. I can attach a tag, to any object, whether or not I’ve used it before, simply by issuing a command like:

fdb tag -a 'book:animal farm (george orwell)' njr/book/lent-to='terrycojones'

Fluidinfo, however, doesn’t see it this way at all. What fdb actually has to do behind the scenes is this:

  1. Check whether the namespace njr/book exists.
  2. If it doesn’t, create a book (sub-)namespace under njr
  3. Check whether the tag lent-to exists in the namespace njr/book
  4. If it doesn’t, create the tag.
  5. Finally, apply to tag njr/book/lent-to to Animal Farm’s object (the one with about tag book:animal farm (george orwell)) with a value of terrycojones.

Thus, from Fluidinfo’s perspective, tag has two completely different meanings.

  • There are actual, concrete tags that have been attached to objects and which usually (but not always) have values—for example, the njr/book/lent-to tag I just attached to the book Animal Farm with value terrycojones.
  • There is the abstract tag, njr/book/lent-to, which may exist even if it hasn’t actually been attached to anything.

My view is that all of this is more-or-less an implementation detail that users certainly shouldn’t have to worry about, and which ideally even developers using the API shouldn’t really have to worry about too much. But the permissions system is absolutely and resolutely grounded in this mass of implementation detail.

Personally, when forced to make the distinction between the two kinds of tags, I prefer to call the tag itself an abstract tag, and to call the individual occurrences of a tag on an object (typically with a value) simple tags. If it’s absolutely necessary to emphasize the distinction, I might call such tag instances taggings or tag instances.

The API, however, calls what I call the abstract tag, a tag, and what I call a tag a tag-value, and in particular the paths it uses to access them by these names.

Seen this way, the structure in the previous tables appears less arbitrary. This is Fluidinfo’s internal view of it:

fi-perms-current-fi.png

As you see, it’s all perfectly consistent, as long as you are very careful about the distinction between the abstract tag the tagging that results when you attach a tag to an object to produce a tag instance (a tag-value pair).

Could We Improve This?

To Terry’s eternal delight, I’m forever coming up with suggestions for how to improve Fluidinfo. I think we could make the permissions system far easier to understand, without changing anything in the underlying architecture by renaming things to have more different names for permissions (actions, as the API calls them) in the API and by removing the need to distinguish between so-called tags and tag-value pairs. (This would go hand-in-hand with my other desire to create tags and namespaces on first use.)

This is my proposed simplication:

fi-perms-proposed.png

This has the same level of granularity as the current scheme everywhere except in the control permissions (bottom right-hand corner), where I propose controlling the pair of existing permissions with together. (I don’t see that it is important to be able to control who can change what are currently regarded as tag permissions separately from what are currently regarded as tag-value permissions; indeed, the belief that these aren’t really different is behind the proposed reorganization anyway.)

So here is what am I proposing changing:

  • The read permission on a namespace is currently called list. This is reasonable, but it seems to me that it would be simpler and more conventional just to call it read. It would also mean that the same action applied to namespaces as to tags.
  • Rather that having a create action on a tag-value, we would simply have a tag action on tags. So to attach a tag, you need tag permission for that tag.
  • Correspondingly, rather than deleting a tag-value, we would simply have an untag action. To remove a tag from an object, you need untag permission for that tag.
  • The current update permission really controls the ability to edit the metadata for a tag (currently, its description). So we would rename this metadata. (I have an ulterior motive for this: update starts with a u, as does untag, and I’d like to be able to distinguish between the main read/write-permissions using their initial letters. But I think “metadata” is easier to understand than “update” anyway.)
  • Finally, as noted above, having simplified the permissions to a set of actions that can be applied either to tags or namespaces, and having removed the need for the /tag-values path, we can merge the control permissions for abstract tags and tags. (We could still maintain two internally: it’s just that there would be no mechanism for changing one without the other.)

Plans for fdb

I have wanted to add the ability to change permissions from the fdb command line for ages, but have always been put off by the complexity of the permissions structure and the likely complexity that would bleed into the command syntax. However, I think the simplification framework above provides a much easier model to work with. My current plan is to implement both fine and coarse controls using the mental model of the revised permissions shown above. fdb will, of course, just map actual changes to the correct underlying permissions unless and until the API changes.

More than this, however, I plan to implement a two-level approach. Normally, I think, people will want to control all the write permissions for tags and namespaces together. So I am imagining a high-level commands that move all the write permissions together, with more granular versions that allow the subcomponents to be changed. The syntax might be something like:

fdb perm r open njr/rating
fdb perm r open-except terrycojones  njr/rating

for read, and

fdb perm w closed-except njr,oniginiri,fxn njr/rating

I think the command would probably issue a warning if the base namespace owner were missed off the exception list, since that would usually be an error.

For more detailed control, you could use, for example,

fdb perm cmtu closed-except njr,oniginiri,fxn njr/rating

to change the permissions for create, metadata, tag and untag but not delete.

There is a slight issue associated with the fact that, in Fluidinfo, it is possible to have both a tag and a namespace that share the same path, and that would be ambiguous in the syntax above. But I’m not sure I care.

I’m less worried about the control permission, which will be changed more rarely, but would probably use a capital C for that.

3 comments:

  1. Anonymous30/5/11 16:15

    I'd suggest using 'd' and 'description' rather than 'm' and 'metadata'. It seems this entire system is really all about metadata and that the term metadata is overloaded here. It may actually create more confusion than it clears up in this instance.

    I like your overall changes. I really like getting rid of 'tag-value' as it seems misnamed to begin with.

    I don't know if 'tag' and 'untag' are quite as helpful as you're suggesting. I rather like create and delete - they are established verbs and don't seem to contribute any cloudiness...

    This is a good conversation. Keep it up!

    ReplyDelete
  2. Terrell

    We could use 'description' instead of 'metadata', but (1) I think it's potentially broader than description and (2) 'd' would clash with 'd' for delete. (Unique single letter abbreviations aren't essential, but they're helpful.)

    I agree that 'create' would be OK for tagging, but 'delete' would clash with the use of 'delete' to remove the abstract tag. It could certainly be another pair of verbs, but I think tag and untag are the most natural when talking about Fluidinfo, so extending to the API seems reasonably natural to me.

    However, if the two deletes were merged then that problem would go away, and I couldn't necessarily be opposed to that at all.

    Nick

    ReplyDelete
  3. Anonymous1/6/11 18:11

    I see your point about 'd' and 'delete'. Perhaps there's a better word besides metadata... I'll keep thinking on it.

    'tag' and 'untag' are actions and work perfectly fine - they're just not as common as create and delete. That said, i agree, they do feel natural.

    ReplyDelete

Labels