Security and Session Properties Enhancements in Diffusion 6.2

Diffusion 6.2 introduces significant improvements to security features and the use of session properties. These include additional features for managing security roles and more control over the use of session properties in authentication and session filtering.

Session properties are values assigned to every client session in the form of key/value pairs. There is a set of fixed session properties assigned to every session and additional user-defined session properties may be added to sessions. Session properties provide detail about client sessions but also allow certain operations to select sessions by means of ‘session filters’ which are backed by a query language for selecting sessions dependent upon the values of their session properties.

In previous releases, the user-defined session properties could be assigned to a session by authentication handlers and also supplied or changed by suitably privileged clients. In 6.2 it is now possible for a client to propose user-defined session properties itself when connecting to  Diffusion, opening up new possibilities for how such properties are used.

Diffusion 6.2 also introduces a new authentication handler interface which is called with a full set of session properties, including those proposed on connection and can change the user-defined properties as well as a subset of the fixed properties.

The examples below show how you can use the new features within a Java client. These new features will also be available in all other client API variants.

Client Proposed Session Properties

The primary advantage of being able to supply user-defined session properties on connection is to allow the client to provide additional information about itself that can then be used by other privileged clients across the application. One of the powerful capabilities within Diffusion is the ability for certain operations to select (or filter) sessions dependent upon their session property settings. So, if connecting clients were to identify their department on connection then it becomes possible to target clients for certain operations depending upon their department. For example, you could send a message to all clients in the “Accounts” department or subscribe them all to a specific set of topics.

Another use for such session properties may be to provide additional credential information that can be checked by the authentication handler.

A client can propose user-defined session properties when it makes a connection to the server as follows:-

    Session session =
        Diffusion.sessions()
            .principal("control").password("password")
            .property("Department", "Accounts")
            .property("City", "London")
            .open("ws://diffusion.example.com:80");

In the above example properties are provided one at a time, but it is also possible to provide a set of user-defined properties as a map:-

    Map<String, String> properties = new HashMap<>();
    properties.put("Department", "Accounts");
    properties.put("City", "London");
    Session session =
        Diffusion.sessions()
            .principal("control").password("password")
            .properties(properties)
            .open("ws://diffusion.example.com:80");

The word ‘propose’ is used rather than ‘provide’ because the properties will only be assigned to the session if the authentication handler allows them. If the properties were simply to provide extra credentials then the authentication handler would probably not assign them to the session at all.

The existing AuthenticationHandler interface does not allow for session properties being passed to it on input, which is one of the reasons why there is a new Authenticator interface in this release. Users of the old AuthenticationHandler interface will not be able to take advantage of client proposed session properties and therefore the interface is deprecated at this release.

New $Roles Session Property

There is a new fixed session property with the key $Roles introduced in 6.2 which contains the security roles assigned to a session. The value of this property is represented as a quoted list of strings. For example, if a session has two roles, role1 and role2 it is represented within the property value as "role1", "role2". To make the handling of this property simpler there are new methods on the Diffusion singleton to decode and encode the property value.

The example below shows how to decode the property value as supplied in a session properties map, add a new role to it, and then encode it again and write back to the map:

    String propertyValue = sessionProperties.get(Session.ROLES);
    Set<String> roles = Diffusion.stringToRoles(propertyValue);
    roles.add("role3");
    sessionProperties.put(Session.ROLES, Diffusion.rolesToString(roles));

The above example shows the use of the Session.ROLES constant which contains the $Roles property key. Constants for all fixed session properties are now available on the Session interface in 6.2.

Having roles available as a session property is not only useful for new authentication handlers but also provides a new way of filtering sessions. The session filtering query language has now been extended to include a new hasRoles clause which can be used to select only those sessions that have specified roles assigned. For example, you could select all sessions that have the role1 and role2 roles assigned using the query string "hasRoles ['role1' 'role2']".

New Authenticator interface

The AuthenticationHandler interface used for authentication handlers in previous releases is passed information in the form of SessionDetails which are being phased out. It also is not provided with session properties. For these reasons, a new Authenticator interface has been introduced at this release that operates solely on the basis of session properties. The old AuthenticationHandler interface has no way of preserving any client proposed session properties that have been provided as described above.

The Authenticator interface is still an authentication handler and is used in exactly the same way as current authentication handlers. It can abstain, deny or allow authentication requests. It can be used for control authentication handlers registered by clients or it can be used for server-side authentication handlers.

An example implementation of an Authenticator used by a control client is shown below. This demonstrates the use of some of the new features introduced in this release.

private static class ExampleControlAuthenticationHandler
    extends Stream.Default
    implements ControlAuthenticator {

    // This map contains the principals that are allowed and their passwords.
    private static final Map<String, byte[]> PASSWORDS = new HashMap<>();
    static {
        PASSWORDS.put("manager", "password".getBytes(Charset.forName("UTF-8")));
        PASSWORDS.put("guest", "asecret".getBytes(Charset.forName("UTF-8")));
        PASSWORDS.put("brian", "boru".getBytes(Charset.forName("UTF-8")));
        PASSWORDS.put("another", "apassword".getBytes(Charset.forName("UTF-8")));
    }

    @Override
    public void authenticate(
        String principal,
        Credentials credentials,
        Map<String, String> sessionProperties,
        Map<String, String> proposedProperties,
        Callback callback) {

        final byte[] passwordBytes = PASSWORDS.get(principal);

        // If the principal is in the table and has provided a valid password
        // then further processing of the properties may be applied
        if (passwordBytes != null &&
            credentials.getType() == Credentials.Type.PLAIN_PASSWORD &&
            Arrays.equals(credentials.toBytes(), passwordBytes)) {

            // The manager principal is allowed all proposed properties
            if ("manager".equals(principal)) {
               // manager allows all proposed properties
               callback.allow(proposedProperties);
            } 
            // The principal brian is allowed all proposed properties and also
            // gets the 'super' role added
            else if ("brian".equals(principal)) {
                final Map<String, String> result =
                    new HashMap<>(proposedProperties);
                final Set<String> roles =
                    Diffusion.stringToRoles(
                        sessionProperties.get(Session.ROLES));
                roles.add("super");
                result.put(Session.ROLES, Diffusion.rolesToString(roles));
                callback.allow(result);
            }
            // All other valid principals are allowed but with no proposed
            // properties assigned to the session
            else {
                callback.allow();
            }
        }
        // If the principal is not in the table it is denied access
        else {
            callback.deny();
        }
    }
}

As well as the name of the principal being authenticated and the credentials provided, the authenticate method is also passed two session property maps called sessionProperties and proposedProperties. The content of these maps differs according to whether the authentication handler is being called for a new session connection or as a result of the session re-authenticating (using changePrincipal).

On initial authentication, sessionProperties will contain a full set of the fixed session properties as assigned by the server. The principal property will be the same as the supplied principal. The roles property will contain the default roles for the principal. This will also include the session identifier, which was not available to authentication handlers in previous releases. The proposedProperties map will contain any user-defined properties proposed by the client when opening a session.

On re-authentication, sessionProperties will contain the current full set of fixed session properties for the session as well as any user-defined properties.  The principal will be the principal currently assigned to the session, which would normally be different from the supplied principal. The roles property will contain the default roles for the new principal. The proposedProperties map will be empty as it is currently not possible to propose user-defined properties when re-authenticating, but this will be provided in a future release.

The authentication handler can simply call the allow() method on the supplied callback. This would accept the connection with the fixed session properties as provided but with no user-defined properties. In the case of re-authentication, the principal property will automatically be changed to the new principal (assuming a value is not assigned by the handler).

To accept user-defined properties, the authentication handler should call the allow(Map<String, String>) method specifying all of the user-defined properties that are to be assigned to the session. The map it supplies does not need to be the full set of proposed properties and may have changed some property values or even added new properties. The authentication handler can also provide values for a subset of the fixed properties (i.e. it can change some fixed property values from those assigned by the server). The most common property that the authentication handler might change is the roles property, but it can also change the principal and all of the properties that are used to specify geographic location details.

Change Roles Operation

The final feature provided in 6.2 which helps in the management of security roles is the new changeRoles method added to the ClientControl feature. This allows a suitably privileged client session to change the security roles assigned to another session. This enables applications to change the permissions for currently connected sessions, perhaps increasing, or even decreasing a client’s capabilities.

Changing the roles of a session will cause the session’s topic selections to be re-evaluated. The session will be unsubscribed from topics to which it no longer has access, and will be subscribed to topics to which it has gained access if they match the session’s topic selections.

The changeRoles method allows the client to specify a set of roles to remove and a set of roles to add.

To change the roles of a specific session, the session identifier is used. The following example shows how to remove role1 from a session and add role2.

    ClientControl clientControl = Session.feature(ClientControl.class);
    clientControl.changeRoles(sessionId, Collections.singleton("role1"), Collections.singleton("role2"));

It is also possible to change the roles of a selection of sessions. In combination with the new $Roles property, this allows roles to be changed on many sessions according to the roles currently assigned. The following example changes every session that has role1 assigned to have role2 instead.

    clientControl.changeRoles("hasRoles ['role1']", Collections.singleton("role1"), Collections.singleton("role2"));

Summary

Release 6.2 includes a number of interrelated enhancements that provide far greater flexibility in managing session authentication of clients. These are summarized as:-

  • Client proposed session properties
    Allowing clients to provide property values when opening a session.
  • New $Roles fixed session property
    Exposing the security roles of clients and allowing them to be used in session filtering
  • New Authenticator Interface
    Enabling the validation and filtering of client proposed session properties as well as the ability to change certain fixed session properties.
  • Change roles operation
    Allowing privileged clients the ability to change the permissions assigned to other clients