Example: Register an authenticator
The following examples use the Diffusion™ API to register a control authenticator with Diffusion Cloud . The examples also include a simple or empty authenticator.
Change the connection URL in the example to that of your Diffusion Cloud service and the name the authenticator registers with, example-handler to be either before-system-handler or after-system-handler depending on when you want the authenticator to be called.
import * as diffusion from 'diffusion';
const PASSWORDS = {
manager: 'password',
guest: 'asecret',
brian: 'boru',
another: 'apassword'
};
/**
* An example of a control authenticator.
*
* This shows a simple example using a table of permitted principals with
* their passwords. It also demonstrates how the handler can change the
* properties of the client being authenticated.
*/
const exampleAuthenticator = {
authenticate: (principal, credentials, sessionProperties, proposedProperties, callback) => {
const password = PASSWORDS[principal];
if (password !== credentials) {
if (principal === 'manager') {
// manager allows all proposed properties
callback.allow(proposedProperties);
} else if (principal === 'brian') {
// brian is allowed all proposed properties and also gets
// the 'super' role added
const result = { ...proposedProperties };
const roles = diffusion.stringToRoles(sessionProperties.ROLES);
roles.add('super');
result.ROLES = diffusion.rolesToString(roles);
callback.allow(result);
} else {
// all others authenticated but ignoring proposed properties
callback.allow();
}
} else {
// Any principal not in the table is denied.
callback.deny();
}
},
onClose: () => {
console.log('The authenticator has disconnected');
},
onError: (err) => {
console.log('An error occurred');
}
};
/**
* This is a control client which registers an authentication handler with a
* Diffusion server.
*/
async function runExample() {
const session = await diffusion.connect({
principal : 'admin',
credentials: 'password'
});
session.security.setAuthenticator('custom-authenticator', exampleAuthenticator)
}
/**
* Copyright © 2021 - 2023 DiffusionData 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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using PushTechnology.ClientInterface.Client.Callbacks;
using PushTechnology.ClientInterface.Client.Factories;
using PushTechnology.ClientInterface.Client.Features.Control.Clients;
using PushTechnology.ClientInterface.Client.Security.Authentication;
using PushTechnology.DiffusionCore.Client.Types;
using static System.Console;
using PushTechnology.ClientInterface.Client.Session;
using System;
namespace PushTechnology.ClientInterface.Example {
/// <summary>
/// Implementation of a client which authenticates other sessions using
/// a registered authentication handler.
/// </summary>
public sealed class AuthenticationControl{
public async Task AuthenticationControlExample(string serverUrl) {
// Connect as a control session
var session = Diffusion.Sessions.Principal( "control" ).Password( "password" )
.CertificateValidation((cert, chain, errors) => CertificateValidationResult.ACCEPT)
.Open(serverUrl);
WriteLine("Opening control session.");
IRegistration registration = null;
try
{
registration = await session.AuthenticationControl.SetAuthenticationHandlerAsync(
"before-system-handler", new Authenticator(), CancellationToken.None );
WriteLine("Authentication handler registered. Authenticator created.");
Diffusion.Sessions.Principal("client")
.Credentials(Diffusion.Credentials.Password("password"))
.CertificateValidation((cert, chain, errors)
=> CertificateValidationResult.ACCEPT)
.Open(serverUrl, new SessionOpenCallback());
await Task.Delay(TimeSpan.FromMilliseconds(2000), CancellationToken.None );
} catch ( TaskCanceledException ) {
//Task was cancelled;
} finally {
WriteLine("Closing control session.");
await registration.CloseAsync();
session.Close();
}
}
/// <summary>
/// Callback used when a session is opened using ISessionFactory.Open
/// </summary>
private sealed class SessionOpenCallback : ISessionOpenCallback
{
public void OnError(ErrorReason errorReason)
=> WriteLine($"An error occurred: {errorReason}");
public void OnOpened(ISession session)
{
WriteLine("Other session opened.");
session.Close();
WriteLine("Other session closed.");
}
}
/// <summary>
/// Basic implementation of the control authenticator.
/// </summary>
private sealed class Authenticator : IControlAuthenticator {
/// <summary>
/// Method which decides whether a connection attempt should be allowed, denied or
/// if another authenticator should evaluate this request.
/// </summary>
/// <param name="principal">The session principal.</param>
/// <param name="credentials">The credentials.</param>
/// <param name="sessionProperties">The session properties.</param>
/// <param name="proposedProperties">The client proposed properties.</param>
/// <param name="callback">The callback.</param>
public void Authenticate(
string principal,
ICredentials credentials,
IReadOnlyDictionary<string, string> sessionProperties,
IReadOnlyDictionary<string, string> proposedProperties,
IAuthenticatorCallback callback ) {
switch ( principal ) {
case "admin": {
WriteLine( "Authenticator allowing connection with proposed properties." );
callback.Allow( proposedProperties );
break;
}
case "client": {
WriteLine( "Authenticator allowing connection with no properties." );
callback.Allow();
break;
}
case "block": {
WriteLine( "Authenticator denying connection." );
callback.Deny();
break;
}
default: {
WriteLine( "Authenticator abstaining." );
callback.Abstain();
break;
}
}
}
/// <summary>
/// Notification of authenticator closure.
/// </summary>
public void OnClose() => WriteLine( "Authenticator closed." );
/// <summary>
/// Notification of error.
/// </summary>
/// <param name="errorReason">Error reason.</param>
public void OnError( ErrorReason errorReason )
=> WriteLine( $"Authenticator received an error: {errorReason}" );
}
}
}
/*******************************************************************************
* Copyright (C) 2023 DiffusionData 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.
*******************************************************************************/
package com.pushtechnology.client.sdk.manual;
import java.util.Map;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import com.pushtechnology.diffusion.client.features.control.clients.AuthenticationControl;
import com.pushtechnology.diffusion.client.features.control.clients.AuthenticationControl.ControlAuthenticator;
import com.pushtechnology.diffusion.client.session.AuthenticationException;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionFactory;
import com.pushtechnology.diffusion.client.types.Credentials;
/**
* This is a control client which registers an authentication handler with a
* Diffusion server.
* <P>
* This uses the 'AuthenticationControl' feature.
*
* @author DiffusionData Limited
*/
public final class AuthenticationControlExample {
public static void main(String[] args) {
// create a SessionFactory and establish a control session
final SessionFactory sessions = Diffusion.sessions();
final Session controlSession = sessions
.principal("control")
.password("password")
.open("ws://localhost:8080");
// set our custom authenticator as the before-system-handler
controlSession.feature(AuthenticationControl.class)
.setAuthenticationHandler("before-system-handler", new MyAuthenticator()).join();
// try to connect with a principal that is not allowed by our custom authenticator
try {
sessions.principal("client").open("ws://localhost:8080");
}
catch (AuthenticationException e) {
System.out.println("This should fail: " + e.getMessage());
}
// connect with a principal that is allowed by our custom authenticator
final Session session = sessions
.principal("diffusion_client")
.password("password")
.open("ws://localhost:8080");
System.out.printf("Connected as %s with id %s\n", session.getPrincipal(), session.getSessionId());
session.close();
controlSession.close();
}
private static final class MyAuthenticator implements ControlAuthenticator {
@Override
public void authenticate(String principal, Credentials credentials,
Map<String, String> sessionProperties,
Map<String, String> proposedProperties,
Callback callback) {
// only allow connections from principals with the 'diffusion_' prefix
if (!principal.startsWith("diffusion_")) {
System.out.println("Principal does not begin with diffusion_ prefix. Connection Rejected.");
callback.deny();
return;
}
System.out.println("Principal begins with diffusion_ prefix. Connection Accepted.");
callback.allow();
}
@Override
public void onClose() { }
@Override
public void onError(ErrorReason errorReason) { }
}
}
/**
* Copyright © 2021 - 2023 DiffusionData 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.
*
*/
#include <stdio.h>
#include <stdlib.h>
#ifndef WIN32
#include <unistd.h>
#else
#define sleep(x) Sleep(1000 * x)
#endif
#include "diffusion.h"
/*
* Authentication handlers are registered with a name, which is typically specified in
* Server.xml
*
* Two handler names are provided by default;
* - before-system-handler
* - after-system-handler
*
* Additional handlers may be specified for Diffusion through the Server.xml file
* and an accompanying Java class that implements the AuthenticationHandler interface.
*
* This example will:
* - Deny all anonymous connections
* - Allow connections where the principal and credentials is in USERS
* - Abstain from all other decisions, thereby letting Diffusion and other
* authentication handlers decide what to do
*/
typedef struct user_credentials_s
{
const char *username;
const char *password;
} USER_CREDENTIALS_T;
// Username/password pairs that this handler accepts.
static const USER_CREDENTIALS_T USERS[] =
{{"manager", "password"}, {"guest", "asecret"}, {"brian", "boru"}, {"another", "apassword"}, {NULL, NULL}};
static int on_authentication_handler_active(
SESSION_T *session,
const DIFFUSION_REGISTRATION_T *registered_handler)
{
// authentication handler is now active
return HANDLER_SUCCESS;
}
static int on_authentication_handler_error(
const DIFFUSION_ERROR_T *error)
{
// An error has occurred in the authentication handler
return HANDLER_SUCCESS;
}
static void on_authentication_handler_close(
void)
{
// Authentication handler has been closed
}
static int on_registration_error(
SESSION_T *session,
const DIFFUSION_ERROR_T *error)
{
// An error has occurred while registering the authentication handler
return HANDLER_SUCCESS;
}
static int on_authenticate(
SESSION_T *session,
const char *principal,
const CREDENTIALS_T *credentials,
const HASH_T *session_properties,
const HASH_T *proposed_session_properties,
const DIFFUSION_AUTHENTICATOR_T *authenticator)
{
if (principal == NULL || strlen(principal) == 0)
{
// Denying anonymous connection (no principal)
diffusion_authenticator_deny(session, authenticator, NULL);
return HANDLER_SUCCESS;
}
if (credentials == NULL)
{
// No credentials specified, abstaining
// We're not an authority for this type of authentication so
// abstain in case some other registered authentication handler can deal
// with the request.
diffusion_authenticator_abstain(session, authenticator, NULL);
return HANDLER_SUCCESS;
}
if (credentials->type != PLAIN_PASSWORD)
{
// Credentials are not PLAIN_PASSWORD, abstaining
diffusion_authenticator_abstain(session, authenticator, NULL);
return HANDLER_SUCCESS;
}
char *password = calloc(credentials->data->len + 1, sizeof(char));
memmove(password, credentials->data->data, credentials->data->len);
int auth_decided = 0;
int i = 0;
while (USERS[i].username != NULL)
{
if (strcmp(USERS[i].username, principal) == 0 && strcmp(USERS[i].password, password) == 0)
{
// Allow
diffusion_authenticator_allow(session, authenticator, NULL);
auth_decided = 1;
break;
}
i++;
}
if (auth_decided == 0)
{
// Abstain
diffusion_authenticator_abstain(session, authenticator, NULL);
}
free(password);
return HANDLER_SUCCESS;
}
int main(
int argc,
char **argv)
{
const char *url = "ws://localhost:8080";
const char *principal = "admin";
const char *password = "password";
CREDENTIALS_T *credentials = credentials_create_password(password);
// Create a session, synchronously
DIFFUSION_ERROR_T error = {0};
SESSION_T *session = session_create(url, principal, credentials, NULL, NULL, &error);
if (session == NULL)
{
fprintf(stderr, "TEST: Failed to create session\n");
fprintf(stderr, "ERR : %s\n", error.message);
return EXIT_FAILURE;
}
// create the authentication handler
DIFFUSION_AUTHENTICATION_HANDLER_T handler = {
.handler_name = "before-system-handler",
.on_active = on_authentication_handler_active,
.on_authenticate = on_authenticate,
.on_error = on_authentication_handler_error,
.on_close = on_authentication_handler_close};
// set the authentication handler
DIFFUSION_AUTHENTICATION_HANDLER_PARAMS_T params = {.handler = &handler, .on_error = on_registration_error};
diffusion_set_authentication_handler(session, params);
// Wait a while before closing the session
sleep(5);
// Close the session, and release resources and memory
session_close(session, NULL);
session_free(session);
credentials_free(credentials);
return EXIT_SUCCESS;
}
Change the URL from that provided in the example to the URL of Diffusion Cloud . Diffusion Cloud service URLs end in diffusion.cloud