How can you start using OACC in your own project?
This document will bring you up to speed on how to install OACC, configure and initialize the OACC database, and define your application model. But before we jump in, let's quickly review the OACC dependencies.
OACC runs on Java™ SE 7 (Java™ version 1.7.0), or higher.
OACC depends on the following external libraries for cryptographic password hashing functions:
Note that if you plan to use OACC's built-in secure password authentication provider and want to prevent a cleanable char[]
password being turned into a temporary String
during Unicode character normalization, you need to include a dependency to ICU4J, the International Components for Unicode library, which is also available as a Maven dependency with groupId: com.ibm.icu
and artifactId: icu4j
.
There are several ways you can get the OACC library:
If your project uses Apache Maven as a build tool, the easiest way to include the latest OACC release into your project is to simply paste the following dependency into your POM file:
<dependency>
<groupId>com.acciente.oacc</groupId>
<artifactId>acciente-oacc</artifactId>
<version>2.0.0</version>
</dependency>
You can download the latest OACC libraries directly from our Downloads page or from GitHub.
If you wish, you can download the latest source from the OACC repository on GitHub and build it yourself.
To compile OACC you will need to include the following dependencies:
To compile and run the OACC unit tests, you will additionally need the following external libraries:
OACC persists all security relationships in database tables and currently supports several relational database management systems. For each supported RDBMS, OACC provides SQL scripts to set up the database schema, tables, user and privileges.
The currently supported database systems are:
The database setup scripts can be found on the OACC Downloads page and consist of four different files that should be executed in the following sequence:
create_database.sql
script abovecreate_database.sql
script abovecreate_schema.sql
script, you need to update this script to reflect the modified (or lack of) database schema, before running itcreate_database.sql
script aboveYou are free to modify the provided scripts to suit your project's needs, as far as the database, schema, user and password are concerned - you'll get a chance to apply your customizations to the OACC configuration separately, after the database setup is complete.
There is a fifth script, drop_tables.sql
, to facilitate removal of OACC constraints, tables and sequences, which you would only run when uninstalling OACC from your project.
These database scripts have been tested against the specified database system and version that their folder is named after. Often they can be run against other (especially higher) versions of the same database, as well, without any issues - but please keep in mind that we didn't actually verify this. Running a database setup script against a completely different database system might be possible between certain databases, but similarly to different SQL dialects, there could be small differences in DDL syntax that would cause an issue.
create_user.sql
script assumes a database user by name of oaccuser
has already been created.
The script will grant that oaccuser
privileges to the required OACC database objects.create_user.sql
script accordingly.null
as the value for the schemaName
parameter when acquiring an AccessControlContext
from SQLAccessControlContextFactory
in your applicationnull
as the value for the schemaName
parameter when acquiring an AccessControlContext
from SQLAccessControlContextFactory
in your applicationPrior to using OACC in your project, you need to perform one more step:
OACC ships with a system initializer utility SQLAccessControlSystemInitializer
that needs to be called once, after you create your tables.
This utility prepares the OACC system for use and creates a default security domain and a super user in that domain.
Initialize OACC by running the SQLAccessControlSystemInitializer
in Java with the following command-line parameters:
dburl
- a JDBC connection URL *dbuser
- the OACC database user name from the create_user.sql
script (oaccuser
)dbpwd
- the password for the OACC database user from the create_user.sql
scriptpwdencryptor
- the password hashing algorithm you want to use (either bcrypt
for an OpenBSD BCrypt implementation or jasypt
for the Jasypt digester)oaccsystempwd
- specify a password for the OACC system resource (aka the "super user"), which you'll need for laterdbschema
- the OACC database schema from the create_schema.sql
script (oacc
), or omit if you didn't run the scriptThe usage to run the initializer from the command line:
$ java com.acciente.oacc.sql.SQLAccessControlSystemInitializer -dburl=<db-url> \
-dbuser=<db-user> \
-dbpwd=<db-password> \
-pwdencryptor=(bcrypt | jasypt) \ # pick one: bcrypt or jasypt
-oaccsystempwd=<OACC-root-password> \
[ -dbschema=<db-schema> ] # [optional - omit if no database schema was created]
If you run the initializer script on tables that have already been initialized OACC will detect this and exit safely without making any changes.
( * ) Note: If you're using MySQL/MariaDB or another RDBMS that by default generates the next sequence value when
INSERT
ing 0
(zero) into an auto-incrementing/identity column, you will have to turn that behavior off when performing
the OACC system initialization, because OACC attempts to create Ids of value zero for its system objects.
In MySQL set sql_mode=NO_AUTO_VALUE_ON_ZERO
via sessionVariables
during connection generation (unless you want to apply this globally to the entire MySQL instance).
A sample JDBC connection url that specifies this for a MySQL connection is shown below:
jdbc:mysql://localhost/oaccdb?sessionVariables=sql_mode='NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'
If you downloaded the OACC source and plan to run OACC unit tests, you need to also configure the included database system specific .properties
file(s) (in the src/test/resources
directory) with the appropriate database settings for your environment, based on the database setup scripts and the OACC initialization parameters used previously.
OACC allows you to manage access control on individual objects in your application. To do so, you have to define or "register" the parts of your application model you'd like to secure in OACC. This section will walk you through the basic concepts and some examples of how to do exactly that.
Before you can start defining your application model in OACC, you need to get a hold of an instance of an OACC AccessControlContext
.
The SQLAccessControlContextFactory
in com.acciente.oacc.sql
provides two methods for this purpose, with the following signatures:
// SQLAccessControlContextFactory
getAccessControlContext(Connection con, String schemaName, SQLProfile sqlProfile, PasswordEncryptor pwdEncryptor);
getAccessControlContext(DataSource ds, String schemaName, SQLProfile sqlProfile, PasswordEncryptor pwdEncryptor);
The schemaName
String argument represents the database schema name used in the create_schema.sql
script, when you set up the database.
The script defaults to OACC
for the schema name, but you need to pass whatever schema name you actually used to the factory, or null
if you didn't run the script or if you were using the scripts provided for MySQL or SQLite.
OACC currently supports RDBMS with seven different flavors of SQL dialects, and can utilize either a recursive or non-recursive query strategy, depending on the database.
Thus you need to specify which dialect and query strategy you're using when obtaining an AccessControlContext
from the factory, by passing in a SQLProfile
parameter.
The SQLProfile
class provides the following options, listed by target database:
DB2_10_5_RECURSIVE
DB2_10_5_NON_RECURSIVE
Oracle_11_2_RECURSIVE
Oracle_11_2_NON_RECURSIVE
PostgreSQL_9_3_RECURSIVE
PostgreSQL_9_3_NON_RECURSIVE
SQLServer_12_0_RECURSIVE
SQLServer_12_0_NON_RECURSIVE
HSQLDB_2_3_NON_RECURSIVE
MySQL_5_6_NON_RECURSIVE
SQLite_3_8_RECURSIVE
SQLite_3_8_NON_RECURSIVE
The PasswordEncryptor
argument is used to configure the password encryption scheme employed by the built-in SQLPasswordAuthenticationProvider
and must correspond to the one specified during initialization in the previous step. Your choices are a BCryptPasswordEncryptor
for an implementation of the OpenBSD BCrypt algorithm or a JasyptPasswordEncryptor
for a Jasypt password digester. You can specify configuration details for each type of encryptor through the static factory methods provided in the respective password encryptor implementations.
Note that if you ever need to switch password encryption algorithms while still being able to authenticate existing users, you can employ a TransitioningPasswordEncryptor
that takes both a source and a target encryptor: it will be able to authenticate old and new resources, but it will store or reset passwords only using the new encryption scheme.
The OACC system resource is the "super user" that was created when you initialized OACC earlier.
We need to authenticate to the AccessControlContext
as the system resource, in order to define parts of your application model, such as the resource classes described in the next step after this one.
The OACC system resource always has zero (0
) as its resourceId, and its super user password is what you specified as the oaccsystempwd
parameter to the SQLAccessControlSystemInitializer
earlier.
The authenticate
method of the AccessControlContext
takes a reference to a Resource
and its corresponding authentication credentials, so you can authenticate as the system resource as follows:
accessControlContext.authenticate(Resources.getInstance(0),
PasswordCredentials.newInstance("yourOaccSystemPassword".toCharArray()));
To put it all together, let's take a look at a sample code snippet that will let us authenticate as the system resource for OACC running on a PostgreSQL database:
import com.acciente.oacc.*;
import com.acciente.oacc.encryptor.bcrypt.*;
import com.acciente.oacc.sql.*;
import java.sql.*;
public class OACCApplicationModelDefinition {
public static void main(String[] args) throws Exception {
// get a connection to the rsf database
String url = "jdbc:postgresql://localhost/oaccdb?user=oaccuser&password=oaccpwd";
try (Connection con = DriverManager.getConnection(url)) {
// get the access control context
AccessControlContext accessControlContext
= SQLAccessControlContextFactory.getAccessControlContext(con,
"OACC",
SQLProfile.PostgreSQL_9_3_RECURSIVE,
BCryptPasswordEncryptor.newInstance(12));
// authenticate as the system resource
accessControlContext.authenticate(Resources.getInstance(0),
PasswordCredentials.newInstance("yourOaccSystemPassword".toCharArray()));
// now we're ready to register our application model with OACC
// (...)
}
}
}
The first step to defining your application model in OACC is to tell OACC about the different types of resources you have.
These different types of application entities you wish to secure in OACC translate into OACC resource classes. For each resource class, you need to decide
The OACC API to create a resource class looks like this:
AccessControlContext {
void createResourceClass(String resourceClassName,
boolean authenticatable,
boolean unauthenticatedCreateAllowed);
}
As an example, we could define the following resource classes:
accessControlContext.createResourceClass("USER", true, true);
accessControlContext.createResourceClass("ADMIN", true, false);
accessControlContext.createResourceClass("DOCUMENT", false, false);
Next, you get to decide what kinds of actions you want to secure on the resource classes you registered above.
OACC lets you define custom permissions to model your application operations, such as "read", "update", "delete", "download", "upload", or whatever you need secured within your project.
A key point to remember is that OACC does not attach any meaning to permissions, such as "view" or "edit" - they are just labels. OACC simply makes it easy for your application to check if a resource has a permission and your application would then allow or disallow an action, accordingly.
The OACC API to create a resource permission looks like this:
AccessControlContext {
void createResourcePermission(String resourceClassName, String permissionName);
}
To expand on our example, we could define the following permissions for our resource classes:
// permissions on resources of class "USER"
accessControlContext.createResourcePermission("USER", "VIEW");
accessControlContext.createResourcePermission("USER", "EDIT");
accessControlContext.createResourcePermission("USER", "DEACTIVATE");
// permissions on resources of class "ADMIN"
accessControlContext.createResourcePermission("ADMIN", "EDIT");
accessControlContext.createResourcePermission("ADMIN", "DEACTIVATE");
// permissions on resources of class "DOCUMENT"
accessControlContext.createResourcePermission("DOCUMENT", "READ");
accessControlContext.createResourcePermission("DOCUMENT", "UPDATE");
accessControlContext.createResourcePermission("DOCUMENT", "COPY");
accessControlContext.createResourcePermission("DOCUMENT", "PRINT");
Note that OACC already provides functionality to deal with the life-cycle of resources, so you typically do not have to define CREATE
or DELETE
permissions for any resource class.
Other built-in permissions that are automatically available to all resource classes are:
*INHERIT
- to inherit permissions from another resource*QUERY
- to inquire about (e.g. retrieve or verify permissions) a resource other than the session resource*IMPERSONATE
- to act on behalf of another authenticatable resource*RESET-CREDENTIALS
- to reset the password of an authenticatable resourceEvery resource in OACC exists inside a domain. Domains serve to scope groups of resources, and most security operations in OACC work within the context of a specified domain.
Domains are incredibly useful to isolate groups of resources in multi-tenant applications, but even if your application is not multi-tenant, we recommend you create at least one domain specific to your application.
The OACC API to create domains looks like this:
AccessControlContext {
void createDomain(String domainName);
void createDomain(String domainName, String parentDomainName);
}
For our example, we could define a single domain as follows:
accessControlContext.createDomain("APP_DOMAIN");
At this point, we've defined a simple model consisting of domain(s), resource classes and permissions, and we're ready to use OACC from our application, as shown in the next section.
For more information regarding the aforementioned API methods, please take a look at the OACC Javadocs. For a much more detailed example on how to integrate OACC into a sample Java application to address several real-world authorization scenarios continue on to the SecureTodo sample application.
The following listing is a simplistic example to illustrate how an application could use OACC to facilitate securing access to the entities in its application model.
import com.acciente.oacc.*;
import com.acciente.oacc.encryptor.bcrypt.*;
import com.acciente.oacc.sql.*;
import java.sql.*;
import java.util.*;
public class OACCSampleApplication {
public static void main(String[] args) throws Exception {
// get a connection to the oacc database
String url = "jdbc:postgresql://localhost/oaccdb?user=oaccuser&password=oaccpwd";
try (Connection con = DriverManager.getConnection(url)) {
// get the access control context
AccessControlContext accessControlContext
= SQLAccessControlContextFactory.getAccessControlContext(con,
"OACC",
SQLProfile.PostgreSQL_9_3_RECURSIVE,
BCryptPasswordEncryptor.newInstance(12));
// create new admin
createAdmin(accessControlContext);
// create new user
createUser(accessControlContext);
// login as admin
loginAdmin(accessControlContext, "adminJoe", "pa55w0rd");
// attempt to update user while logged in as admin
updateUser(accessControlContext, "jsmith");
}
}
private static void createAdmin(AccessControlContext accessControlContext) {
// authenticate as the system resource (the super user) to set up an initial admin
accessControlContext.authenticate(Resources.getInstance(0),
PasswordCredentials.newInstance("yourOaccSystemPassword".toCharArray()));
// persist the admin in your application
// for example:
AppAdmin admin = new AppAdmin.Builder()
.login("adminJoe")
.email("joeBloe@company.com")
.build()
.create();
// create the corresponding OACC resource
final Resource adminResource
= accessControlContext.createResource("ADMIN",
"APP_DOMAIN",
admin.getLogin(),
PasswordCredentials.newInstance("pa55w0rd".toCharArray()));
System.out.println("created new ADMIN resource with Id=" + adminResource.getId());
// grant permissions to query about, view and deactivate any user account, but not to edit it
Set<ResourcePermission> permissions = new HashSet<>();
permissions.add(ResourcePermissions.getInstance(ResourcePermissions.QUERY));
permissions.add(ResourcePermissions.getInstance("VIEW"));
permissions.add(ResourcePermissions.getInstance("DEACTIVATE"));
accessControlContext.setGlobalResourcePermissions(adminResource,
"USER",
"APP_DOMAIN",
permissions);
accessControlContext.unauthenticate();
}
private static void createUser(AccessControlContext accessControlContext) {
// persist the user in your application
// e.g. UserHOME.create("jsmith", "Jane", "Smith", "jsmith@mail.com", userResource.getId())
// for example:
AppUser user = new AppUser.Builder()
.login("jsmith")
.firstName("Jane")
.lastName("Smith")
.email("jsmith@mail.com")
.build()
.create();
// don't have to be authenticated to create users because
// the resource class has the unauthenticatedCreateAllowed-flag set
final Resource userResource
= accessControlContext.createResource("USER",
"APP_DOMAIN",
user.getLogin(),
PasswordCredentials.newInstance("pa$$word1".toCharArray()));
System.out.println("created new USER resource with Id=" + userResource.getId());
}
private static void loginAdmin(AccessControlContext accessControlContext,
String adminLogin,
String password) {
// authenticate as the admin resource
accessControlContext.authenticate(Resources.getInstance(adminLogin),
PasswordCredentials.newInstance(password.toCharArray()));
}
private static void updateUser(AccessControlContext accessControlContext,
String userLogin) {
// assert that the authenticated admin has VIEW permission *before* attempting to load the user
accessControlContext.assertResourcePermissions(accessControlContext.getSessionResource(),
Resources.getInstance(userLogin),
ResourcePermissions.getInstance("VIEW"));
// load the user information and modify the local copy
AppUser user = new AppUser.Finder().findByLogin(userLogin);
user.setEmail("other@mail.com");
// assert that the authenticated admin has EDIT permission *before* attempting to save the user
accessControlContext.assertResourcePermissions(accessControlContext.getSessionResource(),
Resources.getInstance(userLogin),
ResourcePermissions.getInstance("EDIT"));
// save the user
// !NOTE! we won't get here because adminResource doesn't have EDIT permission on the userResource
user.save();
}
}