Using the Security API

by Andrew Taylor <andy@benow.ca>
The BeNOW security API is a set of classes which facilitate the declaration and use of user, role and permission structures. The security structure enforces use case scenarios by specific users. The users can do what they need to, in the easiest way for the developer. In this tutorial you'll learn of users, roles and permissions and how to enforce security designs in code. The javadoc for the security API is online and may be browsed.
Contents
  1. Users
  2. Permissions
  3. Roles
  4. Using Security
  5. Persistence Backends
  6. Creating a UserModule
  7. Secure Applications
Users
Users are the representation of people accessing a multi-user application. A user has a name and other user specific information (ie an email address), in addition to a collection of roles and permissions, and may belong to one or more groups.

Typically, a user and code to run (ie a Runnable) is associated with a SecureThread, and the thread is run. The code can then use the methods from the Security class to check and access security:

Runnable toRun = new Runnable() {
  @Override
  public void run() {
    User u=Security.getCurrentUser();
    System.out.println("This is running as: "+u.getName());
  }
};
Security.runAs("andy", toRun);
The thread would be run as the user 'andy'. Within the body, the current user is accessed and printed. If the user 'andy' did not exist, the method would fail. If the code was not run as administrator, it would fail (as secure threads may not be run as other users). On successful run, the message This is running as: andy would be printed.

Note that the Security accessor class may be used outside of a secured environment. In such a case, the current user is assumed to be the administrator. So, for example:

User u=Security.getCurrentUser();
System.out.println("This is running as: "+u.getName());
would print This is running as: admin as it is not running in a secure environment.

There are three special types of users. These are

Permissions
Permissions may be assigned to roles or users directly. A certain permission is required to perform certain actions. If the user does not directly have the certain permission, or belong to a role having the certain permission, then access is denied to the operations which follow.

Permissions are defined, asserted and checked in code. The following example demonstrates basic permission declaration and assertion.

package org.test;

public class TestObject {
  
  private static final Permission PERM_INVOKE = Security.declareLocalPermission("invoke");

  public void doSomething() {
    PERM_INVOKE.assertPermission();
    System.out.println("Congratulations on having the invoke permission");
  }  
}
The permission is defined via Security.declareLocalPermission(String). When defined, the permission is registered with the security framework (which may involve adding it to a database, etc). The declareLocalPermission creates a permission whose full name is the full name of the class plus the short permission name. In the above example, the full permission created is org.test.TestObject.invoke.

Once declared, permissions can be assigned and asserted. In the above doSomething() method, the permission is asserted by calling the assertPermission() method. In this case, the current user must have the org.test.TestObject.invoke permission to continue. If they do not have the permission, a SecurityException is throw by assertPermission(), otherwise, control returns, processing continues and they see the message.

Roles
A role is a named collection of permissions which can be assigned to users. Roles allow for the grouping of permissions which are useful to certain types of users. When permissions are asserted for users, the assertion succeeds if the user has the permission directly, or if the user has a role which has the permission. For example, an application may create a 'data maintainer' role which allows items to be updated and a 'data manager' role which provides item updating and deletion. The roles could then be assigned as required to users, allowing them to do what they need to do (and no more).
Using Security
The Security class is used as the accessor to security. It has many useful methods, such as: other methods may be seen in the Security javadoc
Persistence Backends
The Security API defines the security interfaces (User,Role,Permission,etc), the Security helper class, and various other utilities. It does not define how the interfaces are implemented, or how information is stored. Specific persistence backends implement the specifics. The repository project implements repository based persistence, while the XML project implements persistence via an XML file. Other persistence backends may be used if alternative storage is desired. With a custom persistence backend, it may be possible to store user information via serialization, remotely via sockets or ldap, etc. See the javadoc for details. The primary implementation is the repository backend.
Creating a UserModule
A UserModule can be used to associate user data with a user. In such a way, the application can save and retrieve information associated with a user without impacting the Security API functionality. Custom application data can be represented in a UserModule descendant. This example shows a custom user module build upon the repository security implementation:
public class AddressUserModule extends UserModuleImpl {

  private static final long serialVersionUID = 1L;

  public String address;
  public String city;
  public String postal;

}
TestUserModule is a UserModule (as UserModuleImpl implements UserModule) and can be associated with the user:
User current = Security.getCurrentUser();
AddressUserModule addrMod = (TestUserModule) current.getModuleByClass(AddressUserModule.class);
addrMod.address = "123 here st";
addrMod.city = "Kaslo";
addrMod.postal = "T2T 2T2";
addrMod.update();
The method getModuleByClass is used to request a AddressUserModule for the user. If there is no such module, a new instance is created. Values can then be assigned and subsequently read from the user module. In such a way, application data can be easily associated with users via the Security API.
Secure Applications
The SecureApplication class exists to facilitate secure command line applications. It takes a username and password and runs the application body as the authenticated user. To use SecureApplication, create and implement a descendant:
public class TestSecureApplication extends SecureApplication {

  static {
    // create an instance of this class (required)
    mainClass = TestSecureApplication.class;
  }

  private Argument dateArg;

  public TestSecureApplication() {
    super("a test secure application");
  }

  @Override
  protected void specifyArguments(ArgumentSpecification spec) {
    dateArg = spec.specArg("--date", "Show date");
    dateArg.setIsSingleton();
  }

  @Override
  protected void secureRun(ArgumentContext ctx) throws Exception {
    System.out.println("This is being run as: " + Security.getCurrentUser().getName());
    if (dateArg.isProvided(ctx))
      System.out.println("The date is: " + new Date());
  }

}
This application can then be started (using a persistence implementation, in this case the repository), with --help to show parameters:
$ java -cp lib/java/derby.jar:lib/java/xerces.jar:lib/java/benow-util.jar:lib/java/log4j.jar:\
lib/java/benow-java.jar:benow-repository.jar:lib/java/benow-security.jar \
test.org.benow.security.TestSecureApplication --help
Loaded xml logging config from: etc/logging.xml
usage: 
test.org.benow.security.TestSecureApplication [--date] [-u val] <-p val> [--help]
a test secure application
  --date:  Show date
  -u:  User to execute as. (default: anonymous)
  -p:  Password to authenticate user. (REQUIRED)
  --help:  Show help
Running it with user, password and date parameters gives:
$ java -cp lib/java/derby.jar:lib/java/xerces.jar:lib/java/benow-util.jar:lib/java/log4j.jar:\
lib/java/benow-java.jar:benow-repository.jar:lib/java/benow-security.jar \
test.org.benow.security.TestSecureApplication -u admin -p adm1n --date
:
This is being run as: admin
The date is: Sat Dec 12 19:34:51 MST 2009
The BeNOW tutorials are a work in progress. If you have any comments or suggestions, please email <andy@benow.ca>.