Example: Register an authentication handler
The following examples use the Diffusion™ API to register a control authentication handler with Diffusion Cloud . The examples also include a simple or empty authentication handler.
Change the connection URL in the example to that of your Diffusion Cloud service and the name the handler registers with, example-handler to be either before-system-handler or after-system-handler depending on when you want the authentication handler 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.diffusion.manual; 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; import java.util.Map; /** * 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 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