Commit faec8844 authored by Pedro Eduardo Trujillo's avatar Pedro Eduardo Trujillo
Browse files

Controla permisos en base a roles de usuario

Amplía funcionalidad de componente Credentials para hacer uso del
contenido del token de usuario para evaluar el nivel de acceso. Se
apoya en un nuevo servicio habilitado en node para extraer los roles
que tiene asignados el usuario. En base a ellos, se comprueba contra
los roles requeridos para cargar una visualización concreta (definidos
en la configuración del layout). Desde que alguno coincida (soporta
también el uso de un valor de id variable en el lado de la configuración
del layout), se concede el acceso y se cargan los componentes del layout
en la vista de detalle.

Adapta WidgetProvider para comunicarse con el renovado Credentials.
Controla también algunos casos de error adicionales.

Añade nuevo servicio en node para recibir un token por parte del usuario
(verificando que es legítimo) y obtener su payload, devolviéndolo al
usuario en formato JSON.

Agrega nueva dependencia en node para el manejo de JWT y su
verificación.

Agrega nueva variable de entorno para recibir el valor de clave pública
en formato PEM usado para validar los tokens de usuario.
parent 5b7fc149
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ OAUTH_CLIENT_SECRET=secretKey \
OID_URL=https://sso.ecomarcan.grafcan.es/realms/ecomarcan-dev/protocol/openid-connect \
OID_CLIENT_ID=app \
OID_CLIENT_SECRET=secretKey \
OID_PEM_PUBLIC_KEY=MIIchangemeAQAB \
API_URL=https://api.ecomarcan.grafcan.es/api \
CONFIG_URL=https://s3.eu-west-1.amazonaws.com/mediastorage.redmicdev/public/config.json \
npm start
+85 −9
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/Deferred'
	, 'src/component/base/_Module'
	, 'src/component/base/_Store'
	, 'src/redmicConfig'
@@ -8,6 +9,7 @@ define([
], function(
	declare
	, lang
	, Deferred
	, _Module
	, _Store
	, redmicConfig
@@ -54,12 +56,16 @@ define([
					GOT_USER_GRANTS_FOR_ENTITY: 'gotUserGrantsForEntity'
				},

				target: redmicConfig.services.profile,
				_profileTarget: redmicConfig.services.profile,
				_tokenPayloadTarget: redmicConfig.services.getTokenPayload,

				_loginPath: '/login'
			};

			lang.mixin(this, this.config, args);

			this.target = [this._profileTarget, this._tokenPayloadTarget];

			this._initializeCredentials();
			this._listenCredentials();
		},
@@ -179,17 +185,40 @@ define([
		},

		_subGetUserGrantsForEntity: function(req) {
			// TODO consultar contra el servidor utilizando id de actividad y usuario

			const entityId = req.entityId,
				entityName = req.entityName,
				accessGranted = Credentials.userIsEditor();
			this._getTokenPayload().then(tokenPayload => this._evaluateUserGrants(tokenPayload, req));
		},

		_getTokenPayload: function() {

			if (!this._tokenPayloadDfd) {
				this._tokenPayloadDfd = new Deferred();
				this._requestTokenPayload();
			}

			return this._tokenPayloadDfd;
		},

		_evaluateUserGrants: function(payload, req) {

			const allowedRoles = this._getAllowedRoles(req),
				userRoles = payload?.resource_access?.['web-client']?.roles;

			const accessGranted = allowedRoles?.some(role => userRoles?.includes(role)) ?? false;

			this._emitEvt('GOT_USER_GRANTS_FOR_ENTITY', {
				accessGranted: accessGranted
				accessGranted
			});
		},

		_getAllowedRoles: function(req) {

			const idPlaceholder = '{id}',
				idValue = req.entityId;

			return req.roles?.map(role => role.replace(idPlaceholder, idValue));
		},

		_onAccessTokenChanged: function(evt) {

			var value = evt.value;
@@ -223,13 +252,53 @@ define([
			//		private

			this._emitEvt('GET', {
				target: this.target
				target: this._profileTarget
			});
		},

		_requestTokenPayload: function() {
			// summary:
			//   Permite obtener el contenido del JWT del usuario pidiéndoselo al servidor para su verificación
			// tags:
			//   private

			const token = Credentials.get('oidAccessToken');

			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this._tokenPayloadTarget,
				params: {
					query: {
						token
					}
				}
			});
		},

		_itemAvailable: function(res) {

			var data = res.data[0];
			this._onProfileItemAvailable(res);
		},

		_dataAvailable: function(res, resWrapper) {

			if (resWrapper.target !== this._tokenPayloadTarget) {
				return;
			}

			this._onTokenPayloadDataAvailable(res);
		},

		_errorAvailable: function(err, status, resWrapper) {

			if (resWrapper.target === this._profileTarget) {
				this._onProfileErrorAvailable();
			}
		},

		_onProfileItemAvailable: function(res) {

			const data = res.data[0];

			if (!data) {
				Credentials.remove('accessToken');
@@ -249,9 +318,16 @@ define([
			});
		},

		_errorAvailable: function(err) {
		_onProfileErrorAvailable: function() {

			this._emitEvt('REQUEST_FAILED');
		},

		_onTokenPayloadDataAvailable: function(res) {

			const payload = res.data;

			this._tokenPayloadDfd?.resolve(payload);
		}
	});
});
+17 −7
Original line number Diff line number Diff line
@@ -59,11 +59,9 @@ define([

		_subGetWidgetsConfig: function(req) {

			const propertyName = req.externalConfigPropName;

			this._currentPropertyName = propertyName;
			this._currentWidgetRequestData = req;

			this._currentEntityData = req;
			const propertyName = req.externalConfigPropName;

			this._emitEvt('GET_EXTERNAL_CONFIG', {propertyName});
		},
@@ -71,10 +69,22 @@ define([

		_onGotExternalConfig: function(evt) {

			const configValue = evt[this._currentEntityData.externalConfigPropName];
			delete this._currentEntityData.externalConfigPropName;
			if (!this._currentWidgetRequestData) {
				return;
			}

			const widgetRequestData = {
				entityId: this._currentWidgetRequestData.entityId,
				entityName: this._currentWidgetRequestData.entityName,
				activityCategory: this._currentWidgetRequestData.activityCategory
			};

			const propertyName = this._currentWidgetRequestData.externalConfigPropName,
				externalConfigValue = evt[propertyName] ?? {};

			delete this._currentWidgetRequestData;

			this._processDetailLayouts(this._currentEntityData, configValue);
			this._processDetailLayouts(widgetRequestData, externalConfigValue);
		},

		_publishLayoutWidget(key, config) {
+8 −5
Original line number Diff line number Diff line
@@ -51,7 +51,7 @@ define([
			}

			const layoutConfig = layout?.config ?? {},
				layoutCheckGrants = layout?.checkGrants ?? false;
				layoutRoles = layout?.roles ?? false;

			layoutConfig.pathVariableId = entityData.entityId;

@@ -60,7 +60,7 @@ define([
				config: layoutConfig
			};

			if (!layoutCheckGrants) {
			if (!layoutRoles) {
				this._prepareLayoutWidgets(processedLayout);
				return;
			}
@@ -71,16 +71,19 @@ define([
				(resolvedGrants) => this._onLayoutGranted(processedLayout, resolvedGrants),
				(rejectedGrants) => this._onLayoutNotGranted(processedLayout, rejectedGrants));

			this._checkUserGrantsForEntityData(entityData, dfd);
			this._checkUserGrantsForEntityData(entityData, layoutRoles, dfd);
		},

		_checkUserGrantsForEntityData: function(entityData, dfd) {
		_checkUserGrantsForEntityData: function(entityData, roles, dfd) {

			this._once(this._buildChannel(this.credentialsChannel, 'GOT_USER_GRANTS_FOR_ENTITY'), (res) => {
				res?.accessGranted ? dfd.resolve(res) : dfd.reject(res);
			});

			this._publish(this._buildChannel(this.credentialsChannel, 'GET_USER_GRANTS_FOR_ENTITY'), entityData);
			this._publish(this._buildChannel(this.credentialsChannel, 'GET_USER_GRANTS_FOR_ENTITY'), {
				...entityData,
				roles
			});
		},

		_onLayoutGranted: function(layout, resolvedGrants) {
+1 −0
Original line number Diff line number Diff line
@@ -230,6 +230,7 @@ define([], function() {
		'getOidToken': '/oid/token',
		'logoutOid': '/oid/revoke',
		'refreshToken': '/oid/refresh',
		'getTokenPayload': '/oid/payload',
		'getExternalConfig': '/config',
		'unit': baseUri + 'units',
		'unitType': baseUri + 'unittypes',
Loading