Setting up

This section describes how to setup and configure SecMan for use in your Apache Isis application, for both authentication (Authenticator SPI) and authorization (Authorizor SPI).

It is also possible to use SecMan in conjunction with the framework’s integration with Shiro, where Shiro takes primary responsibility for authentication, while still using Secman for authorization. This mode allows a separate authentication mechanism such as LDAP to be used. For more details, see setting up with Shiro.

Dependency Management

If your application inherits from the Apache Isis starter app (org.apache.isis.app:isis-app-starter-parent) then that will define the version automatically:

pom.xml
<parent>
    <groupId>org.apache.isis.app</groupId>
    <artifactId>isis-app-starter-parent</artifactId>
    <version>2.0.0-M6</version>
    <relativePath/>
</parent>

Alternatively, import the core BOM. This is usually done in the top-level parent pom of your application:

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.isis.core</groupId>
            <artifactId>isis-core</artifactId>
            <version>2.0.0-M6</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

In addition, add a section for secman’s own BOM:

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.isis.extensions</groupId>
            <artifactId>isis-extensions-secman</artifactId>
            <scope>import</scope>
            <type>pom</type>
            <version>2.0.0-M6</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Dependencies

In the webapp module of your application, add the following dependency:

pom.xml
<dependencies>
    <dependency>
        <groupId>org.apache.isis.extensions</groupId>
        <artifactId>isis-extensions-secman-persistence-XXX</artifactId>    (1)
    </dependency>
    <dependency>
        <groupId>org.apache.isis.extensions</groupId>
        <artifactId>isis-extensions-secman-encryption-jbcrypt</artifactId> (2)
    </dependency>
</dependencies>
1 specify either isis-extensions-secman-persistence-jpa or isis-extensions-secman-persistence-jdo, as required
2 provides an implementation of PasswordEncryptionService

Update AppManifest

In your application’s AppManifest (top-level Spring @Configuration used to bootstrap the app), import the SecMan modules. You will also need to import the fixture module; SecMan uses fixture scripts to seed its entities:

AppManifest.java
@Configuration
@Import({
        ...
        IsisModuleExtSecmanPersistenceXxx.class,        (1)
        IsisModuleExtSecmanEncryptionJbcrypt.class,     (2)

        IsisModuleTestingFixturesApplib.class,          (3)
        ...
})
public class AppManifest {
}
1 specify either IsisModuleExtSecmanPersistenceJdo or IsisModuleExtSecmanPersistenceJpa, as required
2 use Jbcrypt to encrypt passwords
3 fixture script support

In addition, you will probably want to remove the IsisModuleSecurityShiro.class dependency so that SecMan can take care of both authentication and authorisation.

It also possible to use SecMan in conjunction with Shiro, as described here. The primary use case is to support delegated users that Shiro authenticates externally using LDAP, for example.

Configuration Properties

Add the database schema used by the SecMan entities to the configuration file:

application.yml
isis:
  persistence:
    schema:
      auto-create-schemas: isisExtensionsSecman

Optionally, modify the configuration properties for Secman itself:

application.yml
isis:
  extensions:
    secman:
      seed:
        admin:
          user-name: "secman-admin"                     (1)
          password: "pass"                              (1)
          role-name: "isis-ext-secman-admin"            (2)
          namespace-permissions:
            sticky: ...                                 (3)
            additional: ...                             (4)
        regular-user:
          role-name: "isis-ext-secman-user"             (5)
      permissions-evaluation-policy: ALLOW_BEATS_VETO   (6)
      user-menu-me-action-policy: HIDE                  (7)
      user-registration:
        initial-role-names: ...                         (8)
1 indicates the security super-user and password
2 indicates the name of the role granted to this security super-user. This can be any name.
3 the "sticky" namespace permissions granted to the admin role. These cannot be removed (through the UI).
4 any additional namespace permissions to be granted to the admin role. These can be removed (through the UI).
5 indicates the name of the role that should be granted to regular users of the application.
6 if there are conflicted (allow vs veto) permissions at the same scope, then whether the allow wins or the veto wins
7 whether to HIDE, DISABLE or ENABLE the default "me" action provided by the core framework. Normally this should be hidden, because secman’s ApplicationUser broadly replaces the core framework’s UserMemento.
8 if self-user registration is enabled in the viewer, this defines the set of roles to be granted to said user.

This is discussed in more detail below.

SecMan provides a large number of menu actions. You can use menubars.layout.xml to arrange these as you see fit. To get you started, the following fragment adds all of the actions to a "Security" secondary menu:

menubars.layout.xml
<mb3:secondary>
    ...
    <mb3:menu>
        <mb3:named>Security</mb3:named>
        <mb3:section>
            <mb3:named>Users</mb3:named>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationUserMenu" id="userManager"/>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationUserMenu" id="findUsers"/>
        </mb3:section>
        <mb3:section>
            <mb3:named>Roles</mb3:named>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationRoleMenu" id="findRoles"/>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationRoleMenu" id="newRole"/>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationRoleMenu" id="allRoles"/>
        </mb3:section>
        <mb3:section>
            <mb3:named>Permissions</mb3:named>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationPermissionMenu" id="allPermissions"/>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationPermissionMenu" id="findOrphanedPermissions"/>
        </mb3:section>
        <mb3:section>
            <mb3:named>Tenancies</mb3:named>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationTenancyMenu" id="findTenancies"/>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationTenancyMenu" id="newTenancy"/>
            <mb3:serviceAction objectType="isis.ext.secman.ApplicationTenancyMenu" id="allTenancies"/>
        </mb3:section>
    </mb3:menu>
</mb3:secondary>

We also recommend adding the non-production "me" action to the tertiary menu, eg just above the "logout" action:

menubars.layout.xml
<mb3:tertiary>
    <mb3:menu>
        ...
        <mb3:section>
            <mb3:serviceAction objectType="isis.ext.secman.MeService" id="me"/>
            <mb3:serviceAction objectType="isis.security.LogoutMenu" id="logout"/>
        </mb3:section>
    </mb3:menu>
</mb3:tertiary>

Default Roles

With SecMan enabled, it will automatically seed a security super-user and a regular role. It also creates a number of other roles to provide access to specific features of the framework (or its extensions). This seeding is performed by the SeedSecurityModuleService, which calls SeedUsersAndRolesFixtureScript.

The full list of roles set up is summarised here:

  • Available in both production and prototype mode

    • Admin role (as defined in the configuration, see above)

      Admin permissions for Secman itself. This is the role granted to the security super-user, and whose exact name is configured using . This role should therefore be extremely tightly locked down.

    • Regular user role (as defined in the configuration, see above)

      Regular user permissions for Secman. This should be granted to all users (in particular, it includes the ability to logout!)

    • IsisConfigurationRoleAndPermissions.ROLE_NAME

      Access the configuration properties (from the tertiary menu))

  • Available only in prototype mode:

    • IsisAppFeatureRoleAndPermissions.ROLE_NAME

      Browse the application features + permissions (from the "Prototyping" menu).

    • IsisPersistenceJdoMetaModelRoleAndPermissions.ROLE_NAME

      Download the JDO metamodel (from the "Prototyping" menu).

    • IsisExtCommandReplayPrimaryRoleAndPermissions.ROLE_NAME

      Access to the command replay primary menu

    • IsisExtCommandReplaySecondaryRoleAndPermissions.ROLE_NAME

      Access to the command replay secondary menu

    • IsisExtH2ConsoleRoleAndPermissions.ROLE_NAME

      Access the h2 console (from the "Prototyping" menu)

    • IsisViewerRestfulObjectsSwaggerRoleAndPermissions.ROLE_NAME

      Access the swagger UI (from the "Prototyping" menu)

    • IsisSudoImpersonateRoleAndPermissions.ROLE_NAME

      Impersonate other users (from the "Prototyping" menu, and mixins)

The full list can be found by searching for subclasses of AbstractRoleAndPermissionsFixtureScript.

There is no fixture script to setup a role to access the UI features of the applib, or to run fixture scripts; these are included automatically as part of the SecMan’s own "regular user".

Fixture scripts and seed service, to setup users and roles

If prototyping with an in-memory database then you will most likely want to set up some fixture scripts to automatically set up application users.

As noted above, SecMan automatically seeds the built-in roles and users, so it is only necessary to set up roles and users specific to the application.

To save some boilerplate, you can subclass:

You then will need a way to automatically ensure these users/roles are set up on bootstrapping; this is discussed next.

Custom Seed Service

When developing the app you will probably be using the H2 in-memory database, and so you’ll want to ensure that the roles and (perhaps) users are automatically seeded into the database. Even when running in production with a persistent database, running a default set of fixture scripts may be worthwihle.

One option to setup these users/roles would be to use the isis.testing.fixtures.initial-script configuration property. However the purpose of that config property is meant to be to specify a demo scenario for demoing/prototyping, so that’s probably not the smartest approach.

Better is to write a little domain service to do the seeding of security data for you. The service below will set up the default roles and permissions for the framework’s own modules:

SeedUsersAndRoles.java
@Service
@Priority(PriorityPrecedence.MIDPOINT + 10)
@RequiredArgsConstructor(onConstructor_ = {@Inject})
public class SeedSecurityService {

    private final FixtureScripts fixtureScripts;
    private final TransactionService transactionService;

    @EventListener(MetamodelEvent.class)
    public void onMetamodelEvent(final MetamodelEvent event) {
        if (event.isPostMetamodel()) {
            runScripts();
            transactionService.flushTransaction();
        }
    }

    private void runScripts() {
        fixtureScripts.run(new CustomRolesAndUsers());
    }
}

where CustomRolesAndUsers is a top-level fixture script to set up the application-specific users and roles.

Examples

For example, the following scripts could be used to set up access to the domain objects in the HelloWorld starter app:

  • to set up a "user-rw" role with access to everything under the "hello" namespace:

    RoleAndPerms__UserRw.java
    public class RoleAndPerms__UserRw extends AbstractRoleAndPermissionsFixtureScript {
    
        public static final String ROLE_NAME = "user-rw";
    
        public RoleAndPerms__UserRw() {
            super(ROLE_NAME, "Read-write access to entire application");
        }
    
        @Override
        protected void execute(ExecutionContext ec) {
            newPermissions(
                    ApplicationPermissionRule.ALLOW,
                    ApplicationPermissionMode.CHANGING,
                    Can.of(ApplicationFeatureId.newNamespace("hello"))
            );
        }
    }
  • to set up a user bob with access to this role plus all available built-in roles:

    UserToRole__bob_allRoles.java
    public class UserToRole__bob_allRoles extends AbstractUserAndRolesFixtureScript {
    
      public UserToRole__bob_allRoles() {
        super(
          "bob", "pass",          (1)
          "bob@cratchet.com",     (2)
          "/",                    (3)
          AccountType.LOCAL,      (4)
          Can.of(
            RoleAndPerms__UserRw.ROLE_NAME                                            (5)
            , IsisConfiguration.Extensions.Secman.Seed.REGULAR_USER_ROLE_NAME_DEFAULT (6)
            , IsisAppFeatureRoleAndPermissions.ROLE_NAME                   (7)
            , IsisPersistenceJdoMetaModelRoleAndPermissions.ROLE_NAME      (7)
            , IsisExtCommandReplayPrimaryRoleAndPermissions.ROLE_NAME      (7)
            , IsisExtCommandReplaySecondaryRoleAndPermissions.ROLE_NAME    (7)
            , IsisExtH2ConsoleRoleAndPermissions.ROLE_NAME                 (7)
            , IsisViewerRestfulObjectsSwaggerRoleAndPermissions.ROLE_NAME  (7)
            , IsisSudoImpersonateRoleAndPermissions.ROLE_NAME              (7)
            , IsisConfigurationRoleAndPermissions.ROLE_NAME                (7)
          ));
      }
    }
1 username and password. The password is encrypted, not stored in plain text.
2 email address
3 application tenancy
4 local account (rather than delegated, see Setting up with Shiro)
5 application-specific roles
6 regular user access (always required). Note that this expects that the default regular user role has been left as its default value.
7 specific access to framework features, see above

We can also veto individual members:

  • to set up a "no-delete" role that vetoes the ability to delete objects:

    RoleAndPerms__UserRw.java
    public class RoleAndPerms__NoDelete extends AbstractRoleAndPermissionsFixtureScript {
    
        public static final String ROLE_NAME = "no-delete";
    
        public RoleAndPerms__NoDelete() {
            super(ROLE_NAME, "Veto access to deleting HelloWorld objects");
        }
    
        @Override
        protected void execute(ExecutionContext ec) {
            newPermissions(
                    ApplicationPermissionRule.VETO,
                    ApplicationPermissionMode.VIEWING,
                    Can.of(ApplicationFeatureId.newFeature(ApplicationFeatureSort.MEMBER, "hello.HelloWorldObject#delete"))
            );
        }
    }
  • to set up a user "joe" with the "user-rw" and "no-delete" role:

    UserToRole__joe_UserRw_but_NoDelete.java
    public class UserToRole__joe_UserRw_but_NoDelete
        extends AbstractUserAndRolesFixtureScript {
    
      public UserToRole__joe_UserRw_but_NoDelete() {
        super(
          "joe", "pass",
          "joe@italy.com",
          "/ITA",
          AccountType.LOCAL,
          Can.of(
            RoleAndPerms__UserRw.ROLE_NAME                                           (1)
            ,RoleAndPerms__NoDelete.ROLE_NAME                                        (1)
            ,IsisConfiguration.Extensions.Secman.Seed.REGULAR_USER_ROLE_NAME_DEFAULT (2)
        ));
      }
    }
    1 application-specific roles
    2 regular user access (always required)

To seed in fixture scripts we could create a top-level CustomRolesAndUsers script (as mentioned in above). This would then look something like:

CustomRolesAndUsers.java
public class CustomRolesAndUsers extends FixtureScript {

    @Override protected void execute(ExecutionContext executionContext) {
        executionContext.executeChildren(this,
                // roles
                new RoleAndPerms__HelloRw()
                // users
                , new UserToRole__bob_allRoles()
                , new UserToRole__joe_UserRw_but_NoDelete()
        );
    }
}

The custom seed service would then ensure that these users/roles existed on startup.

Creating Users and Roles

Once a super-user admin account has been seeded in, regular users can be created manually by logging in as that security super-user.

Alternatively, users can be created programmatically through ApplicationUserRepository. This will return an ApplicationUser which can then be granted roles, for example using addRoleToUser() API of ApplicationRoleRepository.

User registration (aka Sign-up)

Secman provides an implementation of the UserRegistrationService SPI. This means, if the viewer supports it (eg the Wicket viewer’s sign-up support), then end-users can sign-up to create an account via email. The Secman implementation sets up the user with appropriate initial roles.

The exact roles to setup are specified using configuration property:

application.yaml
isis:
  extensions:
    secman:
      user-registration:
        initial-roles:
          - "self-registered-user-role"
          - "regular-user-role"