Using the Security API
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
- Users
- Permissions
- Roles
- Using Security
- Persistence Backends
- Creating a UserModule
- 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
admin, the administrator. Admin can do anything, that is, they have
all permissions and roles and are in all groups.
anonymous is the user which is used when running in a secure
environment without any user being explicitly specified. The anonymous user
has no permissions, no roles and is in no groups (unless otherwise modified).
Typically, everything which requires any permission will fail for the anonymous
user.
template:registered is a user which cannot be used directly.
It is a template for a newly registered user. Permissions and roles may
be assigned to this template user, and these permissions and roles will
be assigned to newly created users. This allows for new users to be
primed with the required permissions to do what a new user should be able to do.
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:
getCurrentUser(): returns the current user associated with the secure thread. If
not currently in a secure thread, the admin user is returned. If no user has been
assigned to the secure thread, the anonymous user is returned
isAdministratorUser(): returns true if the current user is the admin user
isDefaultUser(): returns true if the current user is the anonymous user
createUser(String): creates a new user with the given name
authenticate(String,String): authenticates with the given username and password,
returning the authenticated user
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>.