Reference Source

js/services/service_activator.js

// NPM IMPORTS
import assert from 'assert'
import _ from 'lodash'

// COMMON IMPORTS
import T               from '../utils/types'
import { is_server }   from '../utils/is_browser'
import Credentials     from '../base/credentials'
import ServiceRequest  from './service_request'
import ServiceResponse from './service_response'


/**
 * Contextual constant for this file logs.
 * @private
 * @type {string}
 */
let context = 'common/services/service_activator'



/**
 * Service activator class.
 * 
 * @author Luc BORIES
 * 
 * @license Apache-2.0
 */
export default class ServiceActivator
{
	/**
	 * Create a service provider.
	 * 
	 * 	API:
	 * 		->activate(arg_application, arg_server, arg_app_svc_cfg):nothing - Activate a service feature for an application.
	 * 
	 * @param {Service} arg_service_instance - service instance.
	 * 
	 * @returns {nothing}
	 */
	constructor(arg_service_instance)
	{
		assert( T.isObject(arg_service_instance) && arg_service_instance.is_service, context + ':bad service object')
		
		this.is_service_activator = true
		
		this.service = arg_service_instance
	}
	
	

	/**
	 * Activate a service feature for an application.
	 * 
	 * @param {ServiceProvider} arg_provider - service provider instance.
	 * @param {Application}     arg_application - application instance.
	 * @param {Server}          arg_server - server instance to bind the service.
	 * 
	 * @returns {nothing}
	 */
	activate(arg_provider, arg_application, arg_server)
	{
		assert(T.isObject(arg_application), context + ':bad application object')
		assert(T.isObject(arg_server) && arg_server.is_server, context + ':bad server object')
		
		assert( arg_provider.server == null, context + ': already activated')
		assert( arg_provider.application == null, context + ': already activated')
		
		assert( is_server(), context + ':service activation is only available on server')
		
		// console.log(context + ':activate:app=' + arg_application.get_name())
		// console.log(context + ':activate:server=' + arg_server.get_name())
		
		arg_provider.server = arg_server
		arg_provider.application = arg_application
		arg_provider.application_server = arg_application.get_name() + '-' + arg_server.get_name()

		// ACTIVATE ON SOCKETIO
		if (arg_server.serverio)
		{
			// console.log(context + ':activate:with socketio')

			this.activate_on_socketio_server(arg_provider, arg_server.serverio)
		}

		// ACTIVATE WITH EXECUTABLE
		if ( T.isObject(arg_provider.exec) && arg_provider.exec.is_executable )
		{
			const routes = arg_provider.get_setting_js('routes')
			// console.log(context + ':activate:with routes', routes)

			if ( T.isNotEmptyArray(routes) )
			{
				const exec_cfg = {
					'routes':routes,
					'server':arg_server
				}
				arg_provider.set_setting('routes', routes)
				arg_provider.exec.prepare(exec_cfg)
				arg_provider.exec.execute(arg_application)
			}
		}
	}



	/**
	 * Activate service on one socketio server for browser request with messages.
	 * 
	 * @param {ServiceProvider} arg_provider - service provider instance.
	 * @param {object} arg_socketio - socketio server.
	 * 
	 * @returns {nothing}
	 */
	activate_on_socketio_server(arg_provider, arg_socketio)
	{
		const runtime = arg_provider.get_runtime()
		const svc_name = arg_provider.service.get_name()
		const serverio_svc = arg_socketio.of(svc_name)
		
		console.log(context + ':activate_on_socketio_server:svc=' + svc_name + ':socket.id=' + serverio_svc.name)
		
		const no_credentials_ops = ['devapt-login', 'devapt-subscribe', 'devapt-unsubscribe', 'devapt-disconnect', 'end', 'devapt-ping', 'devapt-connect']

		const error_values = {
			service:svc_name,
			operation:'unknow',
			has_error:true,
			error:'error?'
		}
		
		const with_credentials_fn = (op_name, socket)=>{
			// console.log(context + ':activate_on_socketio_server:with_credentials_fn:svc=' + svc_name + ':socket.id=' + serverio_svc.name + ' enable operation [' + op_name + '] with credentials')
			
			return (arg_request_plain_object) => {
				// console.log(context + ':activate_on_socketio_server:svc=' + svc_name + ':socket.id=' + serverio_svc.name + ' requested operation [' + op_name + '] with credentials')

				const request = new ServiceRequest(arg_request_plain_object)
				request.set_socket(socket)

				// CHECK REQUEST
				if ( ! T.isObject(request) || ! request.is_service_request)
				{
					error_values.operation = op_name
					error_values.error = 'bad request object'
					const response = new ServiceResponse(error_values)
					socket.emit(op_name, response.get_properties_values())
					arg_provider.error('bad credentials')
					console.error(context + ':activate_on_socketio_server:bad request for method %s of svc %s with data:', op_name, svc_name)
					return
				}

				// CHECK CREDENTIALS
				if ( ! request.has_credentials() )
				{
					error_values.operation = op_name
					error_values.error = 'security failure:no credentials'
					const response = new ServiceResponse(error_values)
					socket.emit(op_name, response.get_properties_values())
					arg_provider.error('bad credentials')
					console.error(context + ':activate_on_socketio_server:bad credentials for method %s of svc %s with data:', op_name, svc_name)
					return
				}
				
				// TEST SECURITY
				// console.log(context + ':on: svc=%s op=%s :arg_credentials', svc_name, key, data.credentials.username)
				const credentials = new Credentials(request.get_credentials())
				runtime.security().authenticate(credentials)
				.then(
					(authenticate_result) => {
						if (authenticate_result)
						{
							arg_provider.debug('authentication success')
							// console.log(context + 'authentication success')
							
							const permission = { resource:svc_name, operation:op_name }
							let authorization_promise = runtime.security().authorize(permission, credentials)
							return authorization_promise.then(
								(authorize_result) => {
									// console.log(context + ':authorize_result', authorize_result)
									if (authorize_result)
									{
										arg_provider.debug('authorization success')
										// console.log(context + ':authorization success')
										const response_promise = arg_provider.produce(request)
										response_promise.then(
											(response)=>{
												socket.emit(op_name,  response.get_properties_values())
											}
										)
										return true
									}
									
									arg_provider.debug('authorization failure')
									console.log(context + ':authorization failure')

									error_values.operation = op_name
									error_values.error = 'security failure:authorization refused'
									const response = new ServiceResponse(error_values)
									socket.emit(op_name, response.get_properties_values())
									return false
								}
							)
							.catch(
								(reason) => {
									arg_provider.debug('authorization error:' + reason)
									console.error(context + ':authorization error:' + reason)

									error_values.operation = op_name
									error_values.error = 'security failure:authorization exception:' + reason
									const response = new ServiceResponse(error_values)
									socket.emit(op_name, response.get_properties_values())
								}
							)
						}
						
						arg_provider.debug('authentication failure')
						console.log(context + 'authentication failure')

						error_values.operation = op_name
						error_values.error = 'security failure:authentication refused'
						const response = new ServiceResponse(error_values)
						socket.emit(op_name, response.get_properties_values())
						return
					}
				)
				.catch(
					(reason) => {
						arg_provider.debug('authentication error:' + reason) // TODO NOT ONLY AUTHORIZ. ERROR
						console.log(context + 'authentication error:' + reason)

						error_values.operation = op_name
						error_values.error = 'security failure:authentication exception:' + reason
						const response = new ServiceResponse(error_values)
						socket.emit(op_name, response.get_properties_values())
					}
				)
			}
		}
		
		const no_credentials_fn = (op_name, socket)=>{
			// console.log(context + ':activate_on_socketio_server:no_credentials_fn:svc=' + svc_name + ':socket.id=' + serverio_svc.name + ' enable operation [' + op_name + '] without credentials')

			return (arg_request_plain_object)=>{
				// console.log(context + ':activate_on_socketio_server:svc=' + svc_name + ':socket.id=' + serverio_svc.name + ' requested operation [' + op_name + '] without credentials')
				// console.log(context + ':activate_on_socketio_server:request', arg_request_plain_object)
				
				const request = new ServiceRequest(arg_request_plain_object)
				request.set_socket(socket)
				
				// CHECK REQUEST
				if ( ! T.isObject(request) || ! request.is_service_request)
				{
					error_values.operation = op_name
					error_values.error = 'bad request object'
					const response = new ServiceResponse(error_values)
					socket.emit(op_name, response.get_properties_values())
					arg_provider.error('bad credentials')
					console.error(context + ':activate_on_socketio_server:bad request for operation %s of svc %s with data:', op_name, svc_name)
					return
				}

				const response_promise = arg_provider.produce(request)
				response_promise.then(
					(response)=>{
						// console.log(context + ':activate_on_socketio_server:svc=' + svc_name + ':socket.id=' + serverio_svc.name + ' reply to operation [' + op_name + '] without credentials with response', response.get_properties_values())
						socket.emit(op_name,  response.get_properties_values())
					}
				)
			}
		}

		serverio_svc.on('connection',
			(socket) => {
				arg_provider.info('activate_on_socketio_server:new connection on /' + svc_name, socket.id)
				
				const ops = arg_provider.get_operations_names()
				_.forEach(ops, 
					(op_name) => {
						// socket.join(socket.id)

						if ( no_credentials_ops.indexOf(op_name) > -1 )
						{
							console.log(context + ':activate_on_socketio_server:svc=[%s] serverio.name=[%s] socket.id=[%s] enable operation=[%s] without credentials', svc_name, serverio_svc.name, socket.id, op_name)

							socket.on(op_name, no_credentials_fn(op_name, socket))
							return
						}

						console.log(context + ':activate_on_socketio_server:svc=[%s] serverio.name=[%s] socket.id=[%s] enable operation=[%s] with credentials', svc_name, serverio_svc.name, socket.id, op_name)
						
						socket.on(op_name, with_credentials_fn(op_name, socket))
					}
				)
			}
		)
	}
}