Commit 152558ee authored by Pedro Eduardo Trujillo's avatar Pedro Eduardo Trujillo
Browse files

Añade login y logout para OpenID y revisa OAuth2

Amplía proceso de login y logout para solicitar y persistir tokens de
acceso en paralelo, tanto del anterior OAuth2 como del nuevo OpenID, con
la idea de migrar progresivamente hacia el segundo.

Mueve la petición de logout al lado del servidor, para mantener la
simetría con respecto a OpenID (que sí lo necesita).

Adapta lógica y configuraciones para dar cabida a la nueva
funcionalidad.

Evita eliminar los tokens de acceso cuando falla alguna de las
peticiones de revocación de token, para no pasar por alto los posibles
errores que puedan ocurrir (hasta ahora, se cerraba la sesión de usuario
en cualquier caso).
parent 73aa4d1c
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -14,10 +14,14 @@ Una vez clonado el repositorio del proyecto en el entorno local de desarrollo y
```sh
npm install

OAUTH_URL=https://redmic.grafcan.es/api/oauth \
OAUTH_URL=https://api.ecomarcan.grafcan.es/api/oauth \
OAUTH_CLIENT_ID=app \
OAUTH_CLIENT_SECRET=secretKey \
API_URL=https://redmic.grafcan.es/api \
OID_URL=https://sso.ecomarcan.grafcan.es/realms/ecomarcan-dev/protocol/openid-connect \
OID_CLIENT_ID=app \
OID_CLIENT_SECRET=secretKey \
API_URL=https://api.ecomarcan.grafcan.es/api \
CONFIG_URL=https://s3.eu-west-1.amazonaws.com/mediastorage.redmicdev/public/config.json \
PRODUCTION=0 \
npm start -- --port=80
```
+4 −2
Original line number Diff line number Diff line
@@ -400,8 +400,10 @@ define([
		_onRequestPermissionError: function(res) {

			var requestedTarget = res.url,
				isGetTokenTarget = requestedTarget.indexOf(redmicConfig.services.getToken) !== -1,
				isNotApiTarget = requestedTarget.indexOf(this._apiUrl) === -1;
				isGetTokenTarget = requestedTarget.includes(redmicConfig.services.getOauthToken) ||
					requestedTarget.includes(redmicConfig.services.getOidToken),

				isNotApiTarget = !requestedTarget.includes(this._apiUrl);

			if (isGetTokenTarget || isNotApiTarget) {
				return;
+81 −30
Original line number Diff line number Diff line
@@ -2,6 +2,8 @@ define([
	'src/redmicConfig'
	, 'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/Deferred'
	, 'dojo/promise/all'
	, 'put-selector'
	, 'src/component/base/_Module'
	, 'src/component/base/_Show'
@@ -17,6 +19,8 @@ define([
	redmicConfig
	, declare
	, lang
	, Deferred
	, PromiseAll
	, put
	, _Module
	, _Show
@@ -48,7 +52,8 @@ define([
				'class': 'userArea',
				idProperty: 'id',
				profileTarget: redmicConfig.services.profile,
				_logoutTarget: redmicConfig.services.logout,
				_oauthLogoutTarget: redmicConfig.services.logoutOauth,
				_oidLogoutTarget: redmicConfig.services.logoutOid,
				repositoryUrl: 'https://gitlab.com/redmic-project/client/web'
			};

@@ -88,7 +93,7 @@ define([

			if (this._checkUserIsRegistered()) {
				this._initializeRegisteredUserArea();
				this.target.push(this._logoutTarget);
				this.target.push(this._oauthLogoutTarget, this._oidLogoutTarget);
			} else {
				this._initializeGuestUserArea();
			}
@@ -149,7 +154,7 @@ define([
				]
			});

			this._showMenu();
			this._prepareMenuToShow();
		},

		_initializeUserImage: function() {
@@ -185,10 +190,10 @@ define([

		_showAreaForRegisteredUser: function() {

			this._once(this._buildChannel(this.credentialsChannel, this.actions.GOT_PROPS),
			this._once(this._buildChannel(this.credentialsChannel, 'GOT_PROPS'),
				lang.hitch(this, this._subDataCredentialsGotProps));

			this._publish(this._buildChannel(this.credentialsChannel, this.actions.GET_PROPS), {
			this._publish(this._buildChannel(this.credentialsChannel, 'GET_PROPS'), {
				dataCredentials: true
			});

@@ -203,7 +208,7 @@ define([
			});
		},

		_showMenu: function() {
		_prepareMenuToShow: function() {

			this._publish(this.listMenu.getChannel('ADD_EVT'), {
				sourceNode: this.domNode
@@ -242,13 +247,46 @@ define([
				return;
			}

			var data = {
				'token': Credentials.get('accessToken')
			this._prepareLogoutDfd();
			this._sendOauthLogoutRequest();
			this._sendOidLogoutRequest();
		},

		_prepareLogoutDfd: function() {

			this._logoutDfds = {};

			this._logoutDfds[this._oauthLogoutTarget] = new Deferred();
			this._logoutDfds[this._oidLogoutTarget] = new Deferred();

			PromiseAll(Object.values(this._logoutDfds)).then(
				lang.hitch(this, this._onLogoutSuccessful),
				lang.hitch(this, this._onLogoutFailed));
		},

		_sendOauthLogoutRequest: function() {

			const data = {
				token: Credentials.get('accessToken')
			};

			this._sendLogoutRequest(data, this._oauthLogoutTarget);
		},

		_sendOidLogoutRequest: function() {

			const data = {
				token: Credentials.get('oidAccessToken')
			};

			this._sendLogoutRequest(data, this._oidLogoutTarget);
		},

		_sendLogoutRequest: function(data, target) {

			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this._logoutTarget,
				target: target,
				query: data,
				requesterId: this.getOwnChannel()
			});
@@ -256,43 +294,56 @@ define([

		_errorAvailable: function(_err, status, resWrapper) {

			var target = resWrapper.target;
			const target = resWrapper.target;

			if (target !== this._logoutTarget) {
			if (target === this.profileTarget) {
				return;
			}

			this._emitEvt('TRACK', {
				event: 'logout_error',
				status: status
			});

			this._startLoading();
			this._removeUserData();
		},

		_removeUserData: function() {

			Credentials.set('accessToken', null);
			this._logoutDfds[target].reject(status);
		},

		_dataAvailable: function(response, resWrapper) {
		_dataAvailable: function(res, resWrapper) {

			var target = resWrapper.target;
			const target = resWrapper.target;

			if (target === this._logoutTarget) {
				this._startLoading();
				this._removeUserData();
			if (target === this.profileTarget) {
				this._onProfileDataAvailable(res.data);
				return;
			}

			var data = response.data;
			this._logoutDfds[target].resolve(res);
		},

		_onProfileDataAvailable: function(data) {

			if (data instanceof Array) {
				data = data[0];
			}

			this._showMenu();
			this._prepareMenuToShow();
		},

		_onLogoutSuccessful: function() {

			this._startLoading();
			this._removeUserData();
		},

		_onLogoutFailed: function(status) {

			this._emitEvt('TRACK', {
				event: 'logout_error',
				status: status
			});

			console.error('Logout failed with status:', status);
		},

		_removeUserData: function() {

			Credentials.set('oidAccessToken', null);
			Credentials.set('accessToken', null);
		},

		_beforeHide: function() {
+63 −28
Original line number Diff line number Diff line
define([
	'alertify'
	, "app/user/views/_ExternalUserBaseView"
	, 'app/user/views/_ExternalUserBaseView'
	, 'src/redmicConfig'
	, "dojo/_base/declare"
	, "dojo/_base/lang"
	, "dojo/text!./templates/Login.html"
	, "src/util/Credentials"
	, 'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/Deferred'
	, 'dojo/promise/all'
	, 'dojo/text!./templates/Login.html'
	, 'src/util/Credentials'
	, 'src/component/base/_Store'
], function(
	alertify
@@ -13,6 +15,8 @@ define([
	, redmicConfig
	, declare
	, lang
	, Deferred
	, PromiseAll
	, template
	, Credentials
	, _Store
@@ -20,16 +24,12 @@ define([

	return declare([_ExternalUserBaseView, _Store], {
		//	Summary:
		//		Vista de login
		//
		//	Description:
		//		Permite identificarse para entrar a la aplicación.

		//		Vista de login, permite identificarse para entrar a la aplicación.

		constructor: function (args) {

			this.config = {
				ownChannel: "login",
				ownChannel: 'login',
				templateProps: {
					templateString: template,
					i18n: this.i18n,
@@ -37,24 +37,33 @@ define([
					_onGuestAccess: lang.hitch(this, this._onGuestAccess),
					_onKeyPress: lang.partial(this._onKeyPress, this)
				},
				target: redmicConfig.services.getToken
				_oauthTarget: redmicConfig.services.getOauthToken,
				_oidTarget: redmicConfig.services.getOidToken
			};

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

		_initialize: function() {

			this.target = [
				this._oauthTarget,
				this._oidTarget
			];
		},

		postCreate: function() {

			this.inherited(arguments);

			// Si hemos entrado anteriormente, pone el correo usado por última vez
			if (!Credentials.userIsGuest("userRole")) {
				this.template.emailInputForm.set("value", Credentials.get("userEmail"));
			if (!Credentials.userIsGuest('userRole')) {
				this.template.emailInputForm.set('value', Credentials.get('userEmail'));
			}
			// Si hemos activado la cuenta anteriormente, informa al usuario
			if (Credentials.has("accountActivated")) {
				alertify.success(this.i18n.accountActivated, "");
				Credentials.remove("accountActivated");
			if (Credentials.has('accountActivated')) {
				alertify.success(this.i18n.accountActivated, '');
				Credentials.remove('accountActivated');
			}
		},

@@ -128,24 +137,50 @@ define([
			//		values private: credenciales para obtener el token
			//

			var data = {
			const data = {
				username: values.email,
				password: values.password
			};

			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this.target,
				options: {
			const options = {
				data: data,
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded'
				}
				}
			};

			this._getTokenDfds = {};

			this._getTokenDfds[this._oauthTarget] = new Deferred();
			this._getTokenDfds[this._oidTarget] = new Deferred();

			PromiseAll(Object.values(this._getTokenDfds)).then(lang.hitch(this, this._onGotTokensSuccessfully));

			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this._oauthTarget,
				options: options
			});

			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this._oidTarget,
				options: options
			});
		},

		_dataAvailable: function(res) {
		_dataAvailable: function(res, resWrapper) {

			const target = resWrapper.target,
				accessToken = res.data.access_token;

			this._getTokenDfds[target].resolve(accessToken);
		},

		_onGotTokensSuccessfully: function(getTokenDfds) {

			const oauthAccessToken = getTokenDfds[0],
				oidAccessToken = getTokenDfds[1];

			this._emitEvt('TRACK', {
				event: 'login',
@@ -154,8 +189,8 @@ define([

			this._startLoading();

			var accessToken = res.data.access_token;
			Credentials.set('accessToken', accessToken);
			Credentials.set('oidAccessToken', oidAccessToken);
			Credentials.set('accessToken', oauthAccessToken);
		},

		_errorAvailable: function(error, status) {
+4 −2
Original line number Diff line number Diff line
@@ -225,9 +225,11 @@ define([], function() {
		'speciesValidity': baseUri + 'speciesvalidities',
		'themes': baseUri + 'themes',
		'thematicType': baseUri + 'thematictypes',
		'getToken': '/oauth/token',
		'getOauthToken': '/oauth/token',
		'logoutOauth': '/oauth/revoke',
		'getOidToken': '/oid/token',
		'logoutOid': '/oid/revoke',
		'getExternalConfig': '/config',
		'logout': baseUri + 'oauth/token/revoke',
		'unit': baseUri + 'units',
		'unitType': baseUri + 'unittypes',

Loading