Roles in Sakai sites

This question comes up on the lists often enough it warrants an article:

“How do I add a new role to all sites in Sakai?”

Introduction

A role is assigned to a participant in a site. There are a few defaults, like access and maintain for project sites, and various others for course and portfolio sites.
These roles have a set of permissions attached to them which various tools use to allow/hide access to certain features of tools.
For example the annc.newpermission controls what users can create announcements. If you are a participant of a site and are assigned the role that has this permission enabled, you can create announcements.

How sites get their roles

Sites in Sakai get their list of roles at creation time, copied from the appropriate !site.template.xxx realm, where xxx is the type of site.
For example, if I was to create a site of type project, then that site will get a copy of the roles defined in the !site.template.project realm.

You can see what roles are defined for the appropriate site types in the Admin Realms tool:

Caveat: In the Admin realms tool, note the !site.template realm (ie no .xxx after it). If you create a site with no type, or of a type that doesn’t match any of the other !site.template.xxx’s, then it will use this template instead.

You’ll also note that out-of-the-box there is no !site.template.project realm. Hence, project sites, by default, get their roles from the !site.template realm.

You can either create a !site.template.project realm, or just use the !site.template realm.

So, now we know that sites take a copy of the roles from their template and they are not inherited, i.e you cannot make a change in the template realm and expect it to propagate to the appropriate sites automatically.

Creating a new role

Suppose you want to create a new role called readonly. You want this role in project sites so that you can assign users this role and they can only read things not add, edit or delete.

In the Admin Realms tools, navigate to !site.template or !site.template.project (see paragraph above), and choose ‘Add Role’. You can also select an existing role and choose ‘Copy Role’ to create a new role based on the permissions in the existing role. Give the role an id, ‘readonly’ might be a good choice, perhaps a description, choose the relevant permissions then click ‘Done’.

You’ll see the new role in your realm. Click ‘Save’ to save your changes to that realm.

Now, any site that you create with the matching type (in this case ‘project’) will have this new role available to assign users to.

Existing sites however, will be unchanged.

Modifying an existing role

To modify an existing role, follow the same process as for creating, but this time select the role, adjust the permissions and save.

The !site.helper realm

This is a special realm whereby you can make temporary changes to all roles in all sites. For example, suppose I need to quickly give every maintain user the ‘content.new’ permission. I could adjust the permissions in the !site.helper realm for that role, and then every site with that role will have the new permission.

Caveat: the roles that you want to adjust must first exist in both the !site.helper realm AND the target site, i.e. you can’t create a new role in the !site.helper realm and have it available in all sites.

Populating new/updated roles to existing sites

So if the !site.helper realm doesn’t cascade new roles and creating new roles in the template doesn’t cascade them either, how do we get new or updated roles into every site? The answer is you need to adjust the realm for every site. And let me tell you, you do not want to do this if you have hundreds or thousands of sites.

So fear not! For I have already challenged and defeated this horrific beast.

The solution might come as a surprise to some who didn’t know this beautiful gem was in the Sakai distribution, the web services!
See my earlier post about how to enable the web services in Sakai and secure them, then read on.
Once you have them enabled, you need to install a web service of mine called copyRole [1].
This is as simple as editing:SAKAI-SRC/webservices/axis/src/webapp/SakaiScript.jws, putting the copyRole method in, saving and rebuilding the webservices project.

You can even edit the deployed SakaiScript.jws (TOMCAT/webapps/sakai-axis/SakaiScript.jws) but you’ll lose your changes if you redeploy.

Then, get the Perl script sync-roles-in-sites.pl [2]. This is a simple script I wrote to take care of iterating over many sites and adding/updating a role or many roles from a template realm, e.g. !site.template to the given sites. Note that the realm for a site is /site/siteid. You’ll see that in the script.

So, open up the script and take a look. You need to set the username and password, urls to your web services (there are two), the template realm, the list of roles and the target sites. It’s all documented.

You’ll also need the Perl module SOAP::Lite which you can get automatically by running:
perl -MCPAN -e 'install SOAP::Lite

You might want to take a look at test-connection.pl [3] before running the sync script, as it will check if your web services are working ok.
Then it’s simply a matter of running the script and watching as your roles and permissions are synchronised!

References:

  1. copyRole.txt
  2. sync-roles-in-sites.pl
  3. test-connection.pl

Enabling web services in Sakai and securing them

An often underused feature of Sakai are the web services. They allow you to perform remote administration of Sakai and are scriptable from just about every language that supports SOAP. You can do anything with the web services, any API that Sakai uses can also be used via the web services. They ship by default, you just need to enable them and start integrating!

Enable the web services

Add this to sakai.properties:
webservices.allowlogin=true

Secure the web services

In Sakai 2.4.x and 2.5.x, the IP filtering needs to be done in in Tomcat via the RemoteAddrValve, or Apache via mod_access.
http://tomcat.apache.org/tomcat-5.5-doc/config/valve.html
http://httpd.apache.org/docs/1.3/mod/mod_access.html

For Tomcat, heres the procedure to filter access by IP addresses for the web services:

  1. cd SAKAI-SRC/webservices/axis/src/webapp/
  2. Create a META-INF directory, and a context.xml in that directory.
  3. In context.xml, put this, customised for your environment:
<?xml version="1.0" encoding="UTF-8"?><Context><Valve className="org.apache.catalina.valves.RemoteAddrValve"allow="127\.0\.0\.1,123\.45\.678\.90"deny=""/><Valve className="org.apache.catalina.valves.AccessLogValve"prefix="webservices." suffix=".log"pattern="common"/></Context>

This will also enable logging via the AccessLogValve.

Now mvn clean install sakai:deploy the web services project.

In 2.6.x, filtering is built in and achieved via the following in sakai.properties:

webservices.allow=localhost,127\.0\.0\.1,192\.168\.[0-9.]+,domain\.somewhere\.ac\.uk,123\.45\.678\.90

Note that for both examples you need to escape the dots via a backslash for the list of addresses.

In 2.6.x+ you can also log authorised and denied attempts via:

webservices.log-allowed=true
webservices.log-denied=true

Once setup, start Tomcat and visit https://your.sakai.server/sakai-axis/SakaiScript.jws?wsdl and you’ll see the list of services supported. I recommend using Mac SOAP Client which gives you a neat GUI to load, browse and run the web services:

For more info about the web services in Sakai and how to use them, including sample code, see the Web Services space on Confluence.

Adding Javadoc for dependencies in an Eclipse project

If you use external dependencies or libraries in an Eclipse project and wanted to link up the Javadocs so you can view them in Eclipse by hovering over a class or method, this tidbit may interest you.

For instance, I’ve added a new dependency to my project but when I go to use it’s methods, in the Javadoc pane I get nothing or it says:

 This element neither has attached source nor attached Javadoc and hence no Javadoc could be found.

Previously, I had to either find the Javadoc online, or build it myself and bookmark it in my browser. This means flipping between applications when I want to read up, and if it was online, what if a net connection isn’t available?

So, you can link up each dependency in your project to it’s corresponding Javadoc and then just click on a method or class and get all the info you need right there.

Here’s how:

1. Get the the Javadoc or build it from source, hopefully as easy as just issuing:

 mvn javadoc:javadoc

if the source uses Maven to build. It will then be located in the target/site/apidocs directory.

2. Put the Javadoc somewhere. I keep mine in a Javadocs folder. Tricky.

3. In Eclipse, click your project, then Build Path > Configure Build Path…

4. Find your dependency in the list, or add a new one.

5. Click the triangle next to the dependency to expand a list of options for each. One is Javadoc location. Highlight this, click Edit and browse for your Javadoc. Click OK and you are done.

Now you can hover over methods from that dependency and get the full Javadoc right there in Eclipse.

Note: There are other methods to do this. If you project uses Maven, you can issue:

mvn eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true

to pull it all into your local Maven repo, however I haven’t found this to work for dependencies that are inherited. I also like to keep my Javadocs in a separate location as I am regularly cleaning out my Maven repo.

Repositioning a ModalWindow in Wicket

Sakai renders the main content of a tool in the portlet iFrame, which can affect the positioning of ModalWindow’s in Wicket since the calculations are based around the height of the viewport, which is mucked up with an iFrame.
Here’s a workaround on how to reposition a ModalWindow component in Wicket.
1. Add these Javascript functions to your page:


function getScroll() {

if (document.all) {
// We are in IE
return top.document.documentElement.scrollTop;
} else {
// In other browsers
return top.pageYOffset;
}
}

function fixWindowVertical() {
var myWindow=Wicket.Window.get();
if(myWindow) {
var top = getScroll() + 50;
myWindow.window.style.top = top + "px";
}
return false;
}

2. In your handler that shows the ModalWindow, add an appendJavascript() to call the fixWindowVertical() function:

final AjaxLink showModalLink = new AjaxLink("modalLink") {
public void onClick(AjaxRequestTarget target) {
showModalLink.show(target);
target.appendJavascript("fixWindowVertical();");
}
};

This will show the ModalWindow, then shift it to 50px below the top of the current viewport, and works in iFrames. I have noticed a very tiny flicker between the original position and then the final position, but only occasionally, and if your page is tall enough the original position will be outside of the viewport so you won’t notice anything at all. Open to better suggestions that work in an iFrame though 🙂

For reference, this is being tracked in the Wicket Jira as WICKET-2059.

Build profiles and Sakai dependencies, revisited and resolved

A few weeks ago I wrote an article about how I was investigating using build profiles in Maven POMs, for building Sakai against either the older style dependencies used in 2.4.x/2.5.x, or the newer kernel dependencies used in 2.6.x/trunk. Because the dependencies are completely different, if you need to build the same tool in either of these, you had to manually change the dependencies in every POM.

That article proposed a solution of adding -P <profile> to the Maven build command. This worked great, but I wanted it to be even more automatic. It was by chance that I noticed a commit come through from Nuno Fernandes who picked up the idea of build profiles and was looking at using them in Site Stats.
We banged away a few emails and have finally come up with a solution to make it completely automatic!
How it works:
 
We needed to check for something that was always going to be present in a kernel based build, and never present in a 2.5.x based build, or vice versa. I tossed up the idea of checking for the the authz/authz-api/pom.xml since that is an integral part of Sakai, is present in 2.5.x, but has been moved to the kernel in 2.6.x+.
Nuno mentioned that he uses (and potentially other developers use as well) a different structure for contrib projects vs core Sakai projects, where the contrib projects are located outside the main Sakai code, and symlinked together via a ‘master’ link:
This was the key. Because Maven can follow symlinks, we can use the ‘master’ project as our launchpad. Enough guff, whats the solution!
 
Solution:
<profiles>
 <profile>
 <!-- Binds to Sakai Kernel K1 - for Sakai 2.6.0+ -->
   <id>K1</id>
   <activation>
     <file>
       <exists>../master/../kernel-deploy/pom.xml</exists>
     </file>
   </activation>
 </profile>
 <profile>
 <!-- Binds to Sakai pre Kernel K1 - for Sakai 2.4.x/2.5.x -->
   <id>pre-K1</id>
   <activation>
     <file>
       <missing>../master/../kernel-deploy/pom.xml</missing>
     </file>
   </activation>
 </profile>
</profiles>
Here we are still checking for the existence (or not) of a file, but with a path that is safe for both our cases, having contrib projects mixed with the main Sakai projects, and having them external and symlinked.
And with this approach, Maven can select the profile automatically without any extra flags!
Muchas gracias to Nuno for his work on this! Check out his post about the different layouts that this approach can cater for as well.