isis-module-security
This module, intended for use within Apache Isis, provides the ability to manage users, roles, and permissions. Users have roles, roles have permissions, and permissions are associated with application features. These features are derived from the Isis metamodel and can be scoped at either a package, class or individual class member. Permissions themselves can either allow or veto the ability to view or change any application feature.
A key design objective of this module has been to limit the amount of permissioning data required. To support this objective:
-
permissions are hierarchical: a class-level permission applies to all class members, while a package-level permission applies to all classes of all subpackages
-
permissions can allow or veto access; thus a role can be granted access to most features, but excluded from selective others
-
permissions are scoped: a member-level permission overrides a class-level permission, a class-level permission overrides a package-level permission; the lower-level package permission overrides a higher-level one (eg
com.mycompany.invoicing
overridescom.mycompany
). -
if there are conflicting permissions at the same scope, then the allow takes precedence over the veto.
The module also supports multi-tenancy. Each user can be associated with a particular tenancy, and Isis can then be configured such that they cannot access data in other tenancies. Moreover, tenancies can be hierarchical; users can (assuming they have been granted permission) edit data in "their" tenancy or sub-tenancies, and can view data in parent tenancies (for example, global reference data/standing data).
The module also provides an implementation of Apache Shiro's AuthorizingRealm. This allows the users/permissions to be used for Isis' authentication and/or authorization.
Authentication is optional; each user is either local or delegated:
-
users with a delegated account type are authenticated through a (configured) delegated authentication realm (for example LDAP). Any other implementation of Shiro's
AuthenticatingRealm
can be used. -
users with a local account type are authenticated through a
PasswordEncryptionService
.
The module provides a default implementation based on jBCrypt, but other implementations can be plugged-in if required.
Domain Model
The above diagram was generated by yuml.me; see appendix at end of page for the DSL.
Screenshots
The following screenshots show an example app's usage of the module, which includes all the services and entities
(users, roles, permissions etc)
provided by the module itself. This example app's domain
also has its own very simple ExampleEntity
entity and corresponding repository.
For further screenshots, see the screenshot tutorial on the wiki.
Automatically Seeds Roles
When the security module starts up, it will automatically (idempotently) seed a number of roles, corresponding permissions and a
default isis-module-security-admin
user. This user has access to the security menu:
One of the roles granted to the isis-module-security-admin
user is the corresponding (similarly named)
isis-module-security-admin
role. It is this that grants all permissions to all classes in the security module itself:
The isis-module-security-regular-user
role grants selected permissions to viewing/changing members of the
ApplicationUser
class (so that a user with this role can view/update their own record):
Add permission at different scopes
Permissions can be created at different scopes or levels (highlighted in the above screenshot).
Permissions created at the package level apply to all classes in all packages and subpackages (that is, recursively).
Permissions defined at the class level take precedence to those defined at the package level.
For example, a user might have allow/viewing at a parent level, but have this escalated to allow/changing for a particular class. Conversely, the class-level permission might veto access.
Permissions can also be defined the member level: action, property or collection. These override permissions defined at either the class- or package-level.
Permissions can ALLOW or VETO access
Permissions can either grant (allow) access or prevent (veto) access. If a user has permissions that contradict each other (for example, they are a member of "roleA" that allows the permission, but also of "roleB" that vetoes the permission) then by default the allow wins. However, this is strategy is pluggable, and the security module can be configured such that a veto would override an allow if required.
Permissions can apply to VIEWING or CHANGING the feature
For a property, "changing" means being able to edit it. For a collection, "changing" means being able to add or remove from it. For an action, "changing" means being able to invoke it.
Note that Isis' Wicket viewer currently does not support the concept of "changing" collections; the work-around is instead create a pair of actions to add/remove instead. This level of control is usually needed anyway.
An allow/changing permission naturally enough implies allow/viewing, while conversely and symmetrically veto/viewing permission implies veto/changing.
Specify package
The list of packages (or classes, or class members) is derived from Isis' own metamodel.
Application users
Application users can have either a local or a delegated account type.
-
Local users are authenticated and authorized through the module's Shiro realm implementation. The users are created explicitly by the administrator.
-
Optionally a delegate authentication realm can be configured; if so then delegated users can be created and their credentials will be authenticated by the delegate authentication realm. Users are created automatically when that user attempts to log in. However, for safety their
ApplicationUser
accounts are created in a disabled state and with no roles, so the administrator is still required to update them.
Once the user is created, then additional information about that user can be captured, including their name and contact details. This information is not otherwise used by the security module, but may be of use to other parts of the application. The users' roles and effective permissions are also shown.
A user can maintain their own details, but may not alter other users' details. An administrator can alter all details, as well as reset a users' password.
If a user is disabled, then they may not log in. This is useful for temporarily barring access to users without having to change all their roles, for example if they leave the company or go on maternity leave.
User Sign-up (Self-Registration)
Apache Isis allows users to sign-up (self-register) with an application provided that:
- the application is correctly configured for the
EmailNotificationService
, by specifyingisis.service.email.sender.address
andisis.service.email.sender.password
configuration properties; and - the application provides an implementation of the
UserRegistrationService
(more on this below).
The sign-up link is shown on the initial login page:
Following the link prompts for an email:
An email is sent to the specified address, with a link to complete the registration:
Completing registration consists of selecting a username and password:
The user can then login:
In the screenshot above note that the user has a default set of permissions. These are set up by the UserRegistrationService
implementation.
The security module provides SecurityModuleAppUserRegistrationServiceAbstract
which provides most of the implementation of this service; the demo app's AppUserRegistrationService
service completes the implementation by specifying the role(s) to assign any new users:
@DomainService
public class AppUserRegistrationService extends SecurityModuleAppUserRegistrationServiceAbstract {
protected ApplicationRole getInitialRole() {
return findRole(ExampleFixtureScriptsRoleAndPermissions.ROLE_NAME);
}
protected Set<ApplicationRole> getAdditionalInitialRoles() {
return Collections.singleton(findRole(ExampleRegularRoleAndPermissions.ROLE_NAME));
}
private ApplicationRole findRole(final String roleName) {
return applicationRoles.findRoleByName(roleName);
}
@Inject
private ApplicationRoles applicationRoles;
}
So, for the demo app at least, any new user has access to the "example-fixture-scripts" role (= the Prototyping menu) and to the "example-regular-role" (= the Tenanted Entities and the Non-Tenanties Entities menus).
Speaking of which...
Application Tenancy
Both application users and domain objects can be associated with an ApplicationTenancy
. For application user's this
is a property of the object, for domain object's this is performed by implementing the WithApplicationTenancy
interface:
public interface WithApplicationTenancy {
ApplicationTenancy getApplicationTenancy();
}
The application can then be configured so that access to domain objects can be restricted based on the respective tenancies of the user accessing the object and of the object itself. The table below summarizes the rules:
object's tenancy | user's tenancy | access |
---|---|---|
null | null | editable |
null | non-null | editable |
/ | / | editable |
/ | /it | visible |
/ | /it/car | visible |
/ | /it/igl | visible |
/ | /fr | visible |
/ | null | not visible |
/it | / | editable |
/it | /it | editable |
/it | /it/car | visible |
/it | /it/igl | visible |
/it | /fr | not visible |
/it | null | not visible |
/it/car | / | editable |
/it/car | /it | editable |
/it/car | /it/car | editable |
/it/car | /it/igl | not visible |
/it/car | /fr | not visible |
/it/car | null | not visible |
To enable this requires a single configuration property to be set, see below.
How to run the Demo App
The prerequisite software is:
- Java JDK 7 (nb: Isis currently does not support JDK 8)
- maven 3 (3.2.x is recommended).
To build the demo app:
git clone https://github.com/isisaddons/isis-module-security.git
mvn clean install
To run the demo app:
mvn antrun:run -P self-host
Then log on using user: isis-module-security-admin
, password: pass
How to configure/use
You can either use this module "out-of-the-box", or you can fork this repo and extend to your own requirements.
Out-of-the-box
Shiro configuration (shiro.ini)
The module includes org.isisaddons.module.security.shiro.IsisModuleSecurityRealm
, an implementation of Apache Shiro's
org.apache.shiro.realm.AuthorizingRealm
class. This realm is intended to be configured as the single realm for Shiro,
but it can optionally have a delegateAuthenticationRealm injected into it.
-
if configured without a delegate realm then
IsisModuleSecurityRealm
deals only with local users and performs both authentication and authorization for them. Authentication is performed against encrypted password. Users with delegate account type will be unable to log in. -
if configured with a delegate realm then
IsisModuleSecurityRealm
deals with both delegated and local users. Authentication of delegated users is performed by the delegate authentication realm, while local users continue to be authenticated in the same way as before, against their encrypted password. Authorization is performed the same way for either account type, by reference to their user roles and those roles' permissions.
For both local and delegated users the realm will prevent a disabled user from logging in.
To configure, update your WEB-INF/shiro.ini
's [main]
section:
[main] isisModuleSecurityRealm=org.isisaddons.module.security.shiro.IsisModuleSecurityRealm authenticationStrategy=org.isisaddons.module.security.shiro.AuthenticationStrategyForIsisModuleSecurityRealm securityManager.authenticator.authenticationStrategy = $authenticationStrategy securityManager.realms = $isisModuleSecurityRealm
If a delegate authentication realm is used, then define it and inject (again, in the [main]
section):
someOtherRealm=... isisModuleSecurityRealm.delegateAuthenticationRealm=$someOtherRealm
where $someOtherRealm
defines some other realm to perform authentication.
Isis domain services (isis.properties)
Update the WEB-INF/isis.properties
:
isis.services-installer=configuration-and-annotation isis.services.ServicesInstallerFromAnnotation.packagePrefix= ...,\ org.isisaddons.module.security,\ ... isis.services = ...,\ org.isisaddons.module.security.dom.password.PasswordEncryptionServiceUsingJBcrypt,\ org.isisaddons.module.security.dom.permission.PermissionsEvaluationServiceAllowBeatsVeto,\ ...
where:
-
the
PasswordEncryptionServiceUsingJBcrypt
is an implementation of thePasswordEncryptionService
. This is mandatory; local users (including the defaultisis-module-security-admin
administrator user) must be authenticated using the password service. If required, any other implementation can be supplied. -
The
PermissionsEvaluationServiceAllowBeatsVeto
is an implementation of thePermissionsEvaluationService
that determines how to resolve conflicting permissions at the same scope. This service is optional; if not present then the module will default to an allow-beats-veto strategy. An alternative implementation ofPermissionsEvaluationServiceVetoBeatsAllow
is also available for use if required; or any other implementation of this interface can be supplied.
There is further discussion of the PasswordEncryptionService
and PermissionsEvaluationService
below.
Tenancy checking (isis.properties)
To enable tenancy checking (as described above, to restrict a user's access to tenanted objects), add the following
in WEB-INF/isis.properties
:
isis.reflector.facets.include=org.isisaddons.module.security.facets.TenantedAuthorizationFacetFactory
Classpath
Finally, update your classpath by adding this dependency in your dom project's pom.xml
:
<dependency> <groupId>org.isisaddons.module.security</groupId> <artifactId>isis-module-security-dom</artifactId> <version>1.8.0</version> </dependency>
If using the PasswordEncryptionServiceUsingJBcrypt
service, also add a dependency on the underlying library:
<dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.3m</version> </dependency>
Check for later releases by searching Maven Central Repo.
"Out-of-the-box" (-SNAPSHOT)
If you want to use the current -SNAPSHOT
, then the steps are the same as above, except:
- when updating the classpath, specify the appropriate -SNAPSHOT version:
<version>1.9.0-SNAPSHOT</version>
- add the repository definition to pick up the most recent snapshot (we use the Cloudbees continuous integration service). We suggest defining the repository in a
<profile>
:
<profile> <id>cloudbees-snapshots</id> <activation> <activeByDefault>true</activeByDefault> </activation> <repositories> <repository> <id>snapshots-repo</id> <url>http://repository-estatio.forge.cloudbees.com/snapshot/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile>
Forking the repo
If instead you want to extend this module's functionality, then we recommend that you fork this repo. The repo is structured as follows:
pom.xml
- parent pomdom
- the module implementation, depends on Isis applibfixture
- fixtures, holding a sample domain objects and fixture scripts; depends ondom
integtests
- integration tests for the module; depends onfixture
webapp
- demo webapp (see above screenshots); depends ondom
andfixture
Only the dom
project is released to Maven Central Repo. The versions of the other modules are purposely left at
0.0.1-SNAPSHOT
because they are not intended to be released.
API and Implementation
The module defines a number of services and default implementations. The behaviour of the module can be adjusted by implementing and registerng alternative implementations.
PasswordEncryptionService
The PasswordEncryptionService
(responsible for authenticating local user accounts) is responsible for
performing a one-way encryption of password to encrypted form. This encrypted version is then stored in the
ApplicationUser
entity's encryptedPassword
property.
The service defines the following API:
public interface PasswordEncryptionService { public String encrypt(final String password); public boolean matches(final String candidate, final String encrypted); }
The PasswordEncryptionServiceUsingJbcrypt
provides an implementation of this service based on Blowfish algorithm. It
depends in turn on org.mindrot:jbcrypt
library; see above for details of updating the classpath to reference this
library.
PermissionsEvaluationService
The PermissionsEvaluationService
is responsible for determining which of a number of possibly conflicting permissions
apply to a target member. It defines the following API:
public interface PermissionsEvaluationService { public ApplicationPermissionValueSet.Evaluation evaluate( final ApplicationFeatureId targetMemberId, final ApplicationPermissionMode mode, final Collection permissionValues);
It is not necessary to register any implementation of this service in isis.properties
; by default a strategy of
allow-beats-veto is applied. However this strategy can be explicitly specified by registering the (provided)
PermissionsEvaluationServiceAllowBeatsVeto
implementation, or alternatively it can be reversed by registering
PermissionsEvaluationServiceVetoBeatsAllow
. Of course some other implementation with a different algorithm may
instead be registered.
Default Roles, Permissions and Users
Whenever the application starts the security module checks for (and creates if missing) the following roles, permissions and users:
isis-module-security-admin
role- allow changing of all classes (recursively) under the
org.isisaddons.module.security.app
package - allow changing of all classes (recursively) under the
org.isisaddons.module.security.dom
package
- allow changing of all classes (recursively) under the
isis-module-security-regular-user
role- allow changing (ie invocation) of the
org.isisaddons.module.security.app.user.MeService#me
action - allow viewing of the
org.isisaddons.module.security.app.dom.ApplicationUser
class - allow changing of the selected "self-service" actions of the
org.isisaddons.module.security.app.dom.ApplicationUser
class
- allow changing (ie invocation) of the
isis-module-security-fixture
role- allow changing of
org.isisaddons.module.security.fixture
package (run example fixtures if prototyping)
- allow changing of
isis-module-security-admin
user- granted
isis-module-security-admin
role
- granted
isis-applib-fixtureresults
role- allow changing of
org.apache.isis.applib.fixturescripts.FixtureResult
class
- allow changing of
This work is performed by the SeedSecurityModuleService
.
Future Directions/Possible Improvements
Limitations in current implementation:
- It is not possible to set permissions on the root package. The workaround is to specify for
org
orcom
top-level package instead.
Ideas for future features:
- enhance the auto-creation of delegated user accounts, so that an initial role can be assigned and the user left as enabled
- users could possibly be extended to include user settings, refactored out from isis-module-settings
- features could possibly be refactored out/merged with isis-module-devutils.
- hierarchical roles
Change Log
1.8.1
- released against Isis 1.8.0; closes #11.1.8.0
- released against Isis 1.8.0.ApplicationTenancy
extended to support hierarchical tenancies, with path as primary key (nb: breaking change), support to make easier to extend (pluggable factories and events for all actions). MeService on TERTIARY menuBar.1.7.0
- released against Isis 1.7.01.6.2
- made more resilient so can be called by an application's own 'security seed' service1.6.1
- support for account types and delegated authentication realm1.6.0
- first release
Legal Stuff
License
Copyright 2014-2015 Dan Haywood
Licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
Dependencies
In addition to Apache Isis, this module depends on:
org.mindrot:jbcrypt
(Apache-like license); only required if thePasswordEncryptionServiceUsingJBcrypt
service is configured.
Maven deploy notes
Only the dom
module is deployed, and is done so using Sonatype's OSS support (see
user guide).
Release to Sonatype's Snapshot Repo
To deploy a snapshot, use:
pushd dom
mvn clean deploy
popd
The artifacts should be available in Sonatype's Snapshot Repo.
Release to Maven Central
The release.sh
script automates the release process. It performs the following:
- performs a sanity check (
mvn clean install -o
) that everything builds ok - bumps the
pom.xml
to a specified release version, and tag - performs a double check (
mvn clean install -o
) that everything still builds ok - releases the code using
mvn clean deploy
- bumps the
pom.xml
to a specified release version
For example:
sh release.sh 1.9.0 \
1.10.0-SNAPSHOT \
dan@haywood-associates.co.uk \
"this is not really my passphrase"
where
$1
is the release version$2
is the snapshot version$3
is the email of the secret key (~/.gnupg/secring.gpg
) to use for signing$4
is the corresponding passphrase for that secret key.
Other ways of specifying the key and passphrase are available, see the pgp-maven-plugin
's
documentation).
If the script completes successfully, then push changes:
git push origin master
git push origin 1.9.0
If the script fails to complete, then identify the cause, perform a git reset --hard
to start over and fix the issue
before trying again. Note that in the dom
's pom.xml
the nexus-staging-maven-plugin
has the
autoReleaseAfterClose
setting set to true
(to automatically stage, close and the release the repo). You may want
to set this to false
if debugging an issue.
According to Sonatype's guide, it takes about 10 minutes to sync, but up to 2 hours to update search.
Appendix: yuml.me DSL
[ApplicationTenancy|name;path{bg:blue}]<0..*children-parent0..1>[[ApplicationTenancy] [ApplicationUser|username{bg:green}]0..*->0..1[ApplicationTenancy] [ApplicationUser]1-0..*>[ApplicationRole|name{bg:yellow}] [ApplicationRole]1-0..*>[ApplicationPermission] [ApplicationUser]->[AccountType|LOCAL;DELEGATED] [ApplicationFeature|fullyQualifiedName{bg:green}]-memberType>0..1[ApplicationMemberType|PROPERTY;COLLECTION;ACTION] [ApplicationFeature]->type[ApplicationFeatureType|PACKAGE;CLASS;MEMBER] [ApplicationPermission{bg:pink}]++->[ApplicationFeature] [ApplicationPermission]->[ApplicationPermissionMode|VIEWING;CHANGING] [ApplicationPermission]->[ApplicationPermissionRule|ALLOW;VETO]