Just a second...

Example: Update the security store

The following examples use the SecurityControl feature in the Diffusion™ API to update the security store.

JavaScript
// Session security allows you to change the principal that a session is authenticated as. It also  allows users to
// query and update server-side security and authentication stores, which control users, roles and permissions.
// This enables you to manage the capabilities that any logged in user will have access to.

// Connect to Diffusion with control client credentials
diffusion.connect({
    host   : 'diffusion.example.com',
    port   : 443,
    secure : true,
    principal : 'control',
    credentials : 'password'
}).then(function(session) {

    // 1. A session change their principal by re-authenticating
    session.security.changePrincipal('admin', 'password').then(function() {
        console.log('Authenticated as admin');
    });

    // 2. The security configuration provides details about roles and their assigned permissions
    session.security.getSecurityConfiguration().then(function(config) {
        console.log('Roles for anonymous sessions: ', config.anonymous);
        console.log('Roles for named sessions: ', config.named);
        console.log('Available roles: ', config.roles);
    }, function(error) {
        console.log('Unable to fetch security configuration', error);
    });

    // 3. Changes to the security configuration are done with a SecurityScriptBuilder
    var securityScriptBuilder = session.security.securityScriptBuilder();

    // Set the permissions for a particular role - global and topic-scoped
    // Each method on a script builder returns a new builder
    var setPermissionScript = securityScriptBuilder.setGlobalPermissions('SUPERUSER', ['REGISTER_HANDLER'])
                                                   .setTopicPermissions('SUPERUSER', '/foo', ['UPDATE_TOPIC'])
                                                   .build();

    // Update the server-side store with the generated script
    session.security.updateSecurityStore(setPermissionScript).then(function() {
        console.log('Security configuration updated successfully');
    }, function(error) {
        console.log('Failed to update security configuration: ', error);
    });

    // 4. The system authentication configuration lists all users & roles
    session.security.getSystemAuthenticationConfiguration().then(function(config) {
        console.log('System principals: ', config.principals);
        console.log('Anonymous sessions: ', config.anonymous);
    }, function(error) {
        console.log('Unable to fetch system authentication configuration', error);
    });

    // 5. Changes to the system authentication config are done with a SystemAuthenticationScriptBuilder
    var authenticationScriptBuilder = session.security.authenticationScriptBuilder();

    // Add a new user and set password & roles.
    var addUserScript = authenticationScriptBuilder.addPrincipal('Superman', 'correcthorsebatterystapler')
                                                   .assignRoles('Superman', ['SUPERUSER'])
                                                   .build();

    // Update the system authentication store
    session.security.updateAuthenticationStore(addUserScript).then(function() {
        console.log('Updated system authentication config');
    }, function(error) {
        console.log('Failed to update system authentication: ', error);
    });
});
                    
.NET
/**
 * Copyright © 2021 Push Technology Ltd.
 *
 * 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.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PushTechnology.ClientInterface.Client.Factories;
using PushTechnology.ClientInterface.Client.Features.Control.Clients.SecurityControl;
using PushTechnology.ClientInterface.Client.Session;
using PushTechnology.ClientInterface.Client.Types;
using static System.Console;

namespace PushTechnology.ClientInterface.Example
{
    /// <summary>
    /// Client implementation that demonstrates how to update the security store.
    /// </summary>
    public sealed class SecurityControl
    {
        public SecurityControl(string serverUrl)
        {
            // Connect as an admin session
            var session = Diffusion.Sessions.Principal("admin").Password("password")
                .CertificateValidation((cert, chain, errors) => CertificateValidationResult.ACCEPT)
                .Open(serverUrl);

            string role = "ADMINISTRATOR";

            IReadOnlyCollection<GlobalPermission> defaultPermissions = null;
            ISecurityConfiguration securityConfig = null;

            try
            {
                //Get the default global permissions for the Admin role.
                securityConfig = await session.SecurityControl.GetSecurityAsync();

                var adminRole = securityConfig.Roles.Where(x => x.Name == role).FirstOrDefault();
                defaultPermissions = adminRole.GlobalPermissions;

                WriteLine($"The Administrator role has the following global permissions by default:");

                foreach (var permission in defaultPermissions)
                {
                    WriteLine($"'{permission}'");
                }
            }
            catch (Exception ex)
            {
                WriteLine($"Failed to get global permissions : {ex}.");
            }

            try
            {
                //Add the following global permissions for the Admin role.
                var permissions = new List<GlobalPermission>(defaultPermissions);
                permissions.AddRange(new[] { GlobalPermission.REGISTER_HANDLER, 
                                             GlobalPermission.VIEW_SESSION });

                WriteLine($"Adding further permissions...");

                string script = 
                    session.SecurityControl.Script.SetGlobalPermissions(role, permissions).ToScript();

                await session.SecurityControl.UpdateStoreAsync(script);
            }
            catch (Exception ex)
            {
                WriteLine($"Failed to set global permissions : {ex}.");
            }
            finally
            {
                session.Close();
            }
        }
    }
}
Java and Android
package com.pushtechnology.diffusion.examples;

import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toCollection;

import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.features.control.clients.SecurityControl;
import com.pushtechnology.diffusion.client.features.control.clients.SecurityControl.Role;
import com.pushtechnology.diffusion.client.features.control.clients.SecurityControl.ScriptBuilder;
import com.pushtechnology.diffusion.client.features.control.clients.SecurityControl.SecurityConfiguration;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.types.PathPermission;

/**
 * An example of using a control client to alter the security configuration.
 * <P>
 * This uses the {@link SecurityControl} feature only.
 *
 * @author Push Technology Limited
 * @since 5.3
 */
public class ControlClientChangingSecurity {

    private static final Logger LOG =
        LoggerFactory.getLogger(
            ControlClientChangingSecurity.class);

    private final SecurityControl securityControl;
    private final ScriptBuilder emptyScript;

    /**
     * Constructor.
     */
    public ControlClientChangingSecurity() {

        final Session session = Diffusion.sessions()
            // Authenticate with a user that has the VIEW_SECURITY and
            // MODIFY_SECURITY permissions.
            .principal("admin").password("password")
            // Use a secure channel because we're transferring sensitive
            // information.
            .open("wss://diffusion.example.com:80");

        securityControl = session.feature(SecurityControl.class);
        emptyScript = securityControl.scriptBuilder();
    }

    /**
     * This will update the security store to ensure that all roles start with a
     * capital letter (note that this does not address changing the use of the
     * roles in the system authentication store).
     *
     * @return a CompletableFuture that completes when the operation succeeds or
     *         fails.
     *
     *         <p>
     *         If the operation was successful, the CompletableFuture will
     *         complete successfully.
     *
     *         <p>
     *         Otherwise, the CompletableFuture will complete exceptionally with
     *         an {@link ExecutionException}. See
     *         {@link SecurityControl#getSecurity()} and
     *         {@link SecurityControl#updateStore(String)} for common failure
     *         reasons.
     */
    public CompletableFuture<Void> capitalizeRoles() {
        return securityControl.getSecurity().thenCompose(this::capitalizeRoles);
    }

    private CompletableFuture<Void> capitalizeRoles(
        SecurityConfiguration configuration) {

        final String script = emptyScript

            .setRolesForAnonymousSessions(
                capitalizeSet(configuration.getRolesForAnonymousSessions()))

            .setRolesForNamedSessions(
                capitalizeSet(configuration.getRolesForNamedSessions()))

            .append(configuration
                // For each role ...
                .getRoles().stream()
                // ... build a script that capitalises that role ...
                .map(this::capitalizeRole)
                /// .. and combine the per-role scripts into one.
                .reduce(emptyScript, (sb1, sb2) -> sb1.append(sb2)))

            .script();

        LOG.info("Sending the following script to the server:\n{}", script);

        return securityControl.updateStore(script)
            // Convert CompletableFuture<?> to CompletableFuture<Void>.
            .thenAccept(ignored -> { });
    }

    private ScriptBuilder capitalizeRole(Role role) {
        final String oldName = role.getName();
        final String newName = capitalizeString(oldName);

        ScriptBuilder builder = emptyScript;

        // Only if new name is different
        if (!oldName.equals(newName)) {
            if (!role.getGlobalPermissions().isEmpty()) {
                builder = builder
                    // Remove global permissions for old role
                    .setGlobalPermissions(oldName, emptySet())
                    // Set global permissions for new role
                    .setGlobalPermissions(
                        newName, role.getGlobalPermissions());
            }

            if (!role.getDefaultPathPermissions().isEmpty()) {
                builder = builder
                    // Remove default path permissions for old role
                    .setDefaultPathPermissions(oldName, emptySet())
                    // Set default path permissions for new role
                    .setDefaultPathPermissions(
                        newName, role.getDefaultPathPermissions());
            }

            builder = builder.append(
                role.getPathPermissions().entrySet().stream().map(
                    entry -> {
                        final String path = entry.getKey();
                        final Set<PathPermission> permissions = entry.getValue();

                        return emptyScript
                            // Remove path permissions for old role
                            .removePathPermissions(oldName, path)
                            // Set path permissions for new role
                            .setPathPermissions(newName, path, permissions);
                    })
                    .reduce(emptyScript, (sb1, sb2) -> sb1.append(sb2)));
        }

        final Set<String> oldIncludedRoles = role.getIncludedRoles();

        if (oldIncludedRoles.isEmpty()) {
            return builder;
        }

        return builder
            // Remove old included roles.
            .setRoleIncludes(oldName, emptySet())

            // Set new roles even if role name did not change as the included
            // roles may be changed.
            .setRoleIncludes(newName, capitalizeSet(oldIncludedRoles));
    }

    private static Set<String> capitalizeSet(Set<String> roles) {
        return roles.stream()
            .map(ControlClientChangingSecurity::capitalizeString)
            .collect(toCollection(TreeSet::new));
    }

    private static String capitalizeString(String role) {
        return Character.toUpperCase(role.charAt(0)) + role.substring(1);
    }

    /**
     * Close the session.
     */
    public void close() {
        securityControl.getSession().close();
    }
}
                    

Change the URL from that provided in the example to the URL of the Diffusion server.