Grails saga #4

June 20, 2013

Well, that was relatively painless. Only sticking point was that a tree link must have a URI that describes it. I have a table of URIs, editable by the user, and the ids 1-1000 are reserved by the boatree app itself as being system constants. So I populate the URI table with two newly-minted URIs:

1: http://biodiversity.org.au/voc/boatree/LinkTypeTerm#TREE_ROOT
2: http://biodiversity.org.au/voc/boatree/LinkTypeTerm#UNSPECIFIED

I will need to author LinkTypeTerm.rdf, which will describe what the URIs mean (ie: that they are used internally by the boatree system for links that are not visible to the user).

Next:

I need to build the “workspace” system. In particular, I have managed to tangle it up with the notion of a user-built tree. I think that I can get away with one “workspace” per user … but then again, maybe not. My previous idea was that a workspace would be built on top of a tree – the “tree that you were working on”. This being the case, we will need multiple per user. The concept was that when a workspace was checked in, only those nodes that were part of the tree that the workspace was built on would be versioned. And it might be nice for users to have different workspaces if they have different areas in the same tree – one for their current edits to Vombatus, another for their work on Cestoda.

A key consideration is the ability for a user to work on something they don’t have access to modify.

We need to make it possible for a user to pull out the AFD Cestoda taxon, fiddle with it, check it in, and then for the curators of AFD to take that checked-in tree and accept it. This is the main motivation behind “user trees”.

Another oopsie is that I have said that the tree roots are invisible to allow for classifications to have multiple unrelated root nodes. A fine example is (again) AFD, which has “root” nodes ANIMALIA and PROTISTA at present. (see http://biodiversity.org.au/afd/mainchecklist). The problem is that you need to be able to edit these roots for precisely this reason.

I think I may need to go back to the notion of a root node and a “tag” node. Tags are nodes that connect to other nodes with fixed or tracking links – they are never versioned. Their purpose is to provide a persistent id for structures that may change over time.

The effect of this is that the “tree” object … damn. I don’t want to hook it to the root node because If I do, I’ll have to write special cases to keep them up-to-date when versioning occurs, and the entire point of the system is to not no have to do that. It’s just that now I have about five node types:

regular nodes
’nuff said
tag nodes
  1. Have a persistent, well-known URI
  2. Are never versioned, are never have versioning sublinks
  3. Are never used as a subnode, except perhaps as the subnode of another tag
  4. When used as the subnode of another tag, the subnodes are understood to form an unordered set
  5. Do I really need this?
tree roots
Just like a tag, except that every regular node “belongs to” one tree, and every workspace is a workspace “of” some particular tree
workspaces
as per above

It’s 1:30AM. Probably time for a break of a few hours.

Advertisements

Grails saga, #3

June 20, 2013

A ha! Need to explicitly set the owner on the belongs-to class, and also put it in the collection. I suspect because it’s marked as nullable:false.

But it’s plain wrong to save the owner if I have put stuff in the collection that can’t be saved. Hibernate invisibly does only half the job I asked it to do, potentially screwing up the data structure.

Ok. Now for the tree link. The top node of the tree – the TreeNode – is not actually the root of the tree. THe root of the tree is an invisible node below this, which potentially gets versioned. The reason this invisible node is invisible is that a “tree” is potentially a holder for a number of smaller trees that have no connection to each other, an example being a set of taxonomies at familiy level that make no attempt to assemble them into higher-order taxa.

Tree links belong to their supernode. To put it another way, a node *is* the content of its tree_node and its set of links to subnodes.There are three types:

  • F – fixed links. A simple node to node link.
  • T – tracking links. If the node is current, then when the subnode is replaced with a new node, the link is updated to point to that new node.
  • V – versioning links. If the node is current, then when the subnode is replaced with a new node, a new node is created and this node is replaced by it.

The entire focus of the system is managing those versioning links – the algorithm is … interesting, because a goal of the system is to do versioning in batches in order to avoid spawning new nodes and ids for every little user action, and because our tree potentially contains diamonds (the structure may one day hold more than just a taxonomy – I think it will serve as a profile store).

In any event, a UserTree is linked by a tracking link to it’s invisible root node. As the user tree is edited and new versions checked in, the top tree node points to the new root.


Grails spring security, very simple names and roles

June 3, 2013

So, I’m writing a webapp. It needs to use a swag of user authentication gear in the existing environment, and actually getting started is a bit of a plate of spaghetti, you know? Before you do bit Y, you must first have bit X in place.

So I wanted to create a component that just had some static userids, ignoring passwords, not accessing the database, so that I could create a skeleton for the app.

first – install the grails spring security plugin.
second – run that s2 thingy, but what we are about to do replaces some of it. In particular, the domain classes become irrelevant. However, we still need that login page and some of the other wiring.

Ok. NOw for our custom bits

First, we need to define roles. These will be the strings we put in our <sec: tags.


import org.springframework.security.core.GrantedAuthority

class IbisGrantedAuthority implements GrantedAuthority {
 public static final String NO_ROLES_S = 'NO_ROLES';
 public static final String ADMIN_S = 'ADMIN';
 public static final String EDITOR_S ='EDITOR';
 public static final String MANAGER_S = 'MANAGER';
	
 public static final IbisGrantedAuthority NO_ROLES = new IbisGrantedAuthority(NO_ROLES_S);
 public static final IbisGrantedAuthority ADMIN = new IbisGrantedAuthority(ADMIN_S);
 public static final IbisGrantedAuthority EDITOR = new IbisGrantedAuthority(EDITOR_S);
 public static final IbisGrantedAuthority MANAGER = new IbisGrantedAuthority(MANAGER_S);
	
 public static final Collection NO_ROLES_G = Arrays.asList([NO_ROLES]));
 public static final Collection ADMIN_G =  Arrays.asList([ADMIN, EDITOR, MANAGER]));
 public static final Collection EDITOR_G = Arrays.asList([EDITOR]));
 public static final Collection MANAGER_G = Arrays.asList([MANAGER]));

 private final String auth;

 IbisGrantedAuthority(String auth) { this.auth = auth;}

 @Override String getAuthority() { auth; }
 String toString() { auth; }
}

I’m using strings, but what I could do is make the class itself an enum. Hmm. Might be neater.

Next, the UserDetails class:


import java.security.Principal
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.User
class IbisUserDetails extends User implements Principal {
 private static final log = LogFactory.getLog(this)

 public static IbisUserDetails buildUser(String username, Collection auth) {
  return new IbisUserDetails(username, 'password', true, true, true, true, auth);
 }

 public IbisUserDetails(String username,
                        String password,
                        boolean enabled,
                        boolean accountNonExpired,
                        boolean credentialsNonExpired,
                        boolean accountNonLocked,
                        Collection authorities) {
 super(username, password, enabled, accountNonExpired, 
       credentialsNonExpired, accountNonLocked, authorities);
 }

 @Override
 public String getName() { return username; }

 @Override
 public void eraseCredentials () {
  //super.eraseCredentials();
 }
}

Note that erase credentials is overridden. This is because grails wipes the password from the user bean after using it, and as I was using static objects, it created all sorts of problems. Of course, now that I am ignoring the password it doesn’t matter,

And we probably want an authentication object to store the results of out authenticating a user. I’m sure there’s an existing class that does this, but we might want something a bit more complex in future.

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;

class IbisAuthentication implements Authentication {
 UsernamePasswordAuthenticationToken c;
 final IbisUserDetails d;

 IbisAuthentication(UsernamePasswordAuthenticationToken c, IbisUserDetails d) {
  this.c = c;
  this.d = d;
 }

 String getName() { return d.username; }
 Collection getAuthorities() {return d.authorities;}
 Object getCredentials() { return c;}
 Object getDetails() { return "no details, really";}
 Object getPrincipal() { return d;}
 boolean isAuthenticated() { return true; }
 void setAuthenticated(boolean b) {
  throw new UnsupportedOperationException();
 }
}

Fantastic. Now we need a userdetails service and an authentication service. The job of the user details service is to implement “here’s a user id. What’s their details?” This impementation defines three static users – a (admin), e (editor), m (manager), and g (guest).

And oops – I tell a lie, it isn’t using static objects at all. After many, many attempts, this is what I wound up with and works.


import org.springframework.dao.DataAccessException
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException

class IbisUserDetailsService implements UserDetailsService {
 @Override
 IbisUserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
  if('a' == username) {
    return IbisUserDetails.buildUser(username, 
                                     IbisGrantedAuthority.ADMIN_G);
  }
  if('e' == username) {
   return IbisUserDetails.buildUser(username, 
                                    IbisGrantedAuthority.EDITOR_G);
  }
  if('m' == username) {
   return IbisUserDetails.buildUser(username, 
                                    IbisGrantedAuthority.MANAGER_G);
  }
  if('g' == username) {
   return IbisUserDetails.buildUser(username, 
                                    IbisGrantedAuthority.NO_ROLES_G);
  }
  throw new UsernameNotFoundException(username);
 }
}

And finally, the Authentication service, the bit that has the job “Here’s some credentials, can you make sense of them?”


class IbisAuthenticationService implements AuthenticationProvider {
 @Autowired
 IbisUserDetailsService ibisUserDetailsService;

 @Override
 boolean supports(Class aClass) {
  if(UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass)) {
   return true;
  }
  return false;
 }

 @Override
 Authentication authenticate(Authentication authentication) throws AuthenticationException {
  if(authentication instanceof UsernamePasswordAuthenticationToken) {
   return authenticate((UsernamePasswordAuthenticationToken) authentication);
  }
  // else if(other token type) { handle that token type }
  else {
    // this should never happen
    throw new IllegalArgumentException("Unrecognised authentication type - this never happens");
  }
 }

 Authentication authenticate(UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
  String userName = (String) authentication.principal;
  String password = (String) authentication.credentials;

  // this code completely ignores password
  try {
   IbisUserDetails d = ibisUserDetailsService.loadUserByUsername(userName);
   return new IbisAuthentication(authentication, d);
  }
  catch(UsernameNotFoundException ex) {
   throw new BadCredentialsException(userName, ex);
  }
 }
}

The unrecognised type never happens because it is spring’s job to check what kinds of authentication I take before sending them to me.

OK! NOw to wire it up!

First, spring beans in conf/spring/resources.groovy:


beans = {
 userDetailsService(au.gov.environment.ibis.grails.ibisauth.IbisUserDetailsService);
 ibisAuthenticationService(au.gov.environment.ibis.grails.ibisauth.IbisAuthenticationService);
}

Defining the user details bean is key, as this name is used by other grails authenticators.

Next, we wire in our authenticator in Config.groovy


grails.plugins.springsecurity.providerNames = [
 'ibisAuthenticationService',
 // 'anonymousAuthenticationProvider',
 'rememberMeAuthenticationProvider'
]

The rememberMe provider is provided by grails, and this provider (I expect) uses userDetailsService, which is why it has to be wired in using that name.

And, that’s about it!

I have a manager page. The code for my main menu bar in the main layout looks like this:


<div style="display: inline; float:left; ">
  <g:link controller='workbench'>Workbench</g:link>
</div>
<sec:ifAnyGranted roles="MANAGER">
  <div style="display: inline;  float:left; ">
    <g:link controller='management'>Manage</g:link>
  </div>
</sec:ifAnyGranted>

And the management item only comes on if the user logs on as ‘a’ or ‘m’. Furthermore, the controller itself is protected with an annotation:


import grails.plugins.springsecurity.Secured

@Secured(["hasRole('MANAGER')"])
@Mixin(ErrorblockMixin)
class ManagementController {

If a user is not logged in and goes to the management URL, spring security cuts in and sends them to the login page.

Took a while, and there’s a lot there, but this is a rock-bottom implmentation of user access control in grails. NOw I can write the rest of the damn app, and wire in our you-beaut single-signon authentication gear later.