Presenting: OpenWiki Invitation
I am happy to announce that I have implemented, tested and put into production, the invitation solution I wrote about this morning. Here is an article describing the solution in more detail.
The problem
In OpenWiki, I've implemented web-based wiki access control administration for end-users. An user can create a new wiki on the fly, and at the same time decide which other users will have access to this new wiki. Giving away access to a wiki, requires the end-user to know an identifier of the person that he/she want to grant access to the wiki. Sometimes this is easy, my identifier is andreas@uninett.no. Sometimes it is a bit more difficult, like at an example university, users have identifiers like op002@uiu.no. Yesterday, I asked David Simonsen, about his identity identifier within the wayf.dk federation, and he had to look it up, and it was (something like) wudyfiusdf@orphanage.wayf.dk.
I am using eduPersonPrincipalName, which is sometimes easy to remember and understand. Instead I could have used eduPersonTargetedId, which for sure had been "impossible" to use, when setting up access control for a group of users.
So, as I introduced the problem in the first place, by implementing end-user ACL administration, I felt obligated to provide some kind of solution to this identity mess.
The Solution
When you create a new wiki or click to edit a wiki you created earlier you get to the wiki access control setup page. Here you have several options:
- Adding users by their identifier (eduPersonPrincipalName). Useful, but problematic, as described above.
- Adding users by filters of federation attribute matching rules.
- New!! Adding users by giving users an invitation token.
So, we'll discuss the new point. What is an invitation token? The token is formed as an URL, and the possession of this URL, or the token parameter, gives the holder the abibility to login with his identity and grant himself permission to the wiki. For security reasons the token is valid only in 72 hours from the moment it was created. Each time the administrator visits the access control setup page, two new tokens are created; one that gives the holder the ability to grant read access, and another token that represent write access respectively.
The screenshot below shows how this part of the access control setup page looks like:

How does this work in practice? Say, I want to give a colleague read permission to this wiki. Then I start by copying the read access token URL, and paste it into an e-mail:

Then I send the e-mail. Usually the reason why we have access control in the first place is because we want to protect the content of the wiki - therefore we send the e-mail encrypted. Obviously, the token it self allows the holder to gain access to the wiki, so the token need to be kept confidential, until it has timed out (after 72 hours).
The receiver of the e-mail, clicks on the URL, and will be prompted by the federation login page (or Single Sign-On is allowing the user to automatically log on). Then the invitation script knows the identifier of the user, and it has a token that gives access to a specific wiki. The script parses and validates the token, and if the token is valid, the user is granted access. This is how it looks to the user:

The user clicks the link to the wiki and have access :)

The Mathematics
I assume the blog readers are above average interested in technical details, so here follows some information about the token itself.
The token is encoded as hex, and consists of two parts, splitted by a dash '-' separator: an offset and an checksum. I'll explain both the offset and the checksum.
If you just want to create a checksum that is valid for a specific wiki for an unlimited time, we can do:
$checksum = sha1($secretSalt . '|' . $wikiname );
We want to bound the token to a specific permission, either read or write, so we do:
$permission = 'read';
$checksum = sha1($secretSalt . '|' . $wikiname . '|' . $permission );
Then, here comes the challenge; we want the checksum to be valid only for a limited time. If we want the checksum to be valid in one second only, we could do:
$checksum = sha1($secretSalt . '|' . $wikiname . '|' . $permission . '|' . time() );
Let's assume the time() function returns the time in seconds. The current time in seconds will change every second, and hence the checksum above will be valid for one second only.
We want instead the checksum to be valid for 1 hour:
$timeslot = floor(time() / 3600);
$checksum = sha1($secretSalt . '|' . $wikiname . '|' . $permission . '|' . $timeslot );
Here the timeslot variable will be identical for an hour, and then change to a new value, and then make the checksum invalid. Good, BUT; what if you generate the checksum 17:55? The checksum is valid for an hour 17-18, but past is irrelevant to the user. The user want a checksum that is valid from one hour from now. We need to calculate an offset.
We calculate the offset like this:
$offset = time() % 3600; // % means modulus
Now, we create a token, that consists of both the offset and the checksum:
$token = dechex($offset) . '-' . $checksum;
Now, how can this token be validated? First we parse the token to get the offset and the checksum. Then we calculate the correct checksum using the wikiname, permission, secretSalt and the offset as input, like this:
$timeslot = floor(time() - $offset / 3600);
$correctChecksum = sha1($secretSalt . '|' . $wikiname . '|' . $permission . '|' . $timeslot );
if ($checksum == $correctChecksum) grantUserPermissionToWiki();
And if the checksum provided by the user matches the correct checksum using current time, and the offset provided by the user, we grant the user access.
To create a URL with a token, we point to the invitation script, and include the name of the wiki, the permission and the token. Like this:
https://openwiki.feide.no/invite.php?token=2643d-9bb31ff0c39e8e4475c9786...
The Code
The OpenWiki code is open source, but I have not updated the code available online for a while. If you are interested, I'll dump the code on you.
The token generation code, I blogged about earlier this year (embedding the code).
The Next Step
Right now, there are no central components that deals with this kind of invitation service, it's up to each one of the service providers within a federation to deal with invitation and access control. I've a plan to make a proof of concept for a new model. Here is how it goes:
I'm introducing a new component in the infrastructure, a Ad-hoc Group Management Service. This service works exactly like the invitation solution outlined in the above sections, but that's all it does. It lets users join groups, administer groups and invite others to join groups. As long as you see this service isolated, it's useless.

The interesting part is if you look at how this service can share information about the groups you are member of with all the web-based applications you use... If I create a group named "Feide Core Developers" to setup access control for the subversion repository, it would be great if I could re-use that group when I created a wiki.
Therefore I suggest adding an SAML 2.0 Attribute Authority Role to the Ad-hoc Group Management Service. Other SAML 2.0 Service Providers may then query information from the group server about what groups are you are member of. The SAML 2.0 Attribute Profile is an appropriate protocol for transferring that kind of information.

But what about privacy? We don't want all applications to be able to ask which group is andreas@uninett.no member of. We want to let the user control all exchange of group information. No information sent without consent. At the same time, we don't want the application to ask about the attributes of andreas@uninett.no, (may be the application does not even know my eduPersonPrincipalName, but only targetedID) but instead the application should ask about the group membership of "the current user". To achieve that we put an layer of OAuth ontop of the SAML 2.0 AA exchange. Within OAuth we hoook up consent.

- Andreas Åkre Solberg [1]'s blog
- Login to post comments
Twelve and a half hours from
Twelve and a half hours from "I'm going to do this" to "I did this". Pretty good, Andreas :-)
See you next week...