diff --git a/back/methods/account/change-password.js b/back/methods/account/change-password.js new file mode 100644 index 000000000..b2afd5402 --- /dev/null +++ b/back/methods/account/change-password.js @@ -0,0 +1,64 @@ + +module.exports = Self => { + Self.remoteMethod('changePassword', { + description: 'Changes the user password', + accepts: [ + { + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + }, { + arg: 'id', + type: 'Number', + description: 'The user id', + http: {source: 'path'} + }, { + arg: 'oldPassword', + type: 'String', + description: 'The old password', + required: true + }, { + arg: 'newPassword', + type: 'String', + description: 'The new password', + required: true + } + ], + returns: { + type: 'Boolean', + root: true + }, + http: { + path: `/:id/changePassword`, + verb: 'PATCH' + } + }); + + Self.changePassword = async function(ctx, id, oldPassword, newPassword) { + let params = [id, oldPassword, newPassword]; + await Self.rawSql(`CALL account.user_changePassword(?, ?, ?)`, params); + + /* + const ldap = require('ldapjs'); + + let ldapConf = { + url: 'ldap://domain.local:389', + dn: 'cn=admin,dc=domain,dc=local', + password: '123456' + }; + + await new Promise((reject, resolve) => { + let client = ldap.createClient({url: ldapConf.url}); + + client.bind(ldapConf.dn, ldapConf.password, err => { + if (err) + reject(err); + else + resolve(); + }); + }); + */ + + return true; + }; +}; diff --git a/back/methods/account/set-password.js b/back/methods/account/set-password.js new file mode 100644 index 000000000..027649548 --- /dev/null +++ b/back/methods/account/set-password.js @@ -0,0 +1,33 @@ + +module.exports = Self => { + Self.remoteMethod('setPassword', { + description: 'Sets the user password', + accepts: [ + { + arg: 'id', + type: 'Number', + description: 'The user id', + http: {source: 'path'} + }, { + arg: 'newPassword', + type: 'String', + description: 'The new password', + required: true + } + ], + returns: { + type: 'Boolean', + root: true + }, + http: { + path: `/:id/setPassword`, + verb: 'PATCH' + } + }); + + Self.setPassword = async function(id, newPassword) { + let params = [id, newPassword]; + await Self.rawSql(`CALL account.user_setPassword(?, ?)`, params); + return true; + }; +}; diff --git a/back/methods/account/specs/change-password.spec.js b/back/methods/account/specs/change-password.spec.js new file mode 100644 index 000000000..9f1130df5 --- /dev/null +++ b/back/methods/account/specs/change-password.spec.js @@ -0,0 +1,9 @@ +const app = require('vn-loopback/server/server'); + +describe('account changePassword()', () => { + it('should throw an error when old password is wrong', async() => { + let req = app.models.Account.changePassword(null, 1, 'wrongOldPass', 'newPass'); + + await expectAsync(req).toBeRejected(); + }); +}); diff --git a/back/methods/account/specs/set-password.spec.js b/back/methods/account/specs/set-password.spec.js new file mode 100644 index 000000000..c76fd52b8 --- /dev/null +++ b/back/methods/account/specs/set-password.spec.js @@ -0,0 +1,15 @@ +const app = require('vn-loopback/server/server'); + +describe('account changePassword()', () => { + it('should throw an error when password does not meet requirements', async() => { + let req = app.models.Account.setPassword(1, 'insecurePass'); + + await expectAsync(req).toBeRejected(); + }); + + it('should update password when it passes requirements', async() => { + let req = app.models.Account.setPassword(1, 'Very$ecurePa22.'); + + await expectAsync(req).toBeResolved(); + }); +}); diff --git a/back/model-config.json b/back/model-config.json index dc5cde217..22d0e2327 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -38,6 +38,9 @@ "ImageCollectionSize": { "dataSource": "vn" }, + "Language": { + "dataSource": "vn" + }, "Province": { "dataSource": "vn" }, diff --git a/back/models/account.js b/back/models/account.js index a0b08dd57..b74a14997 100644 --- a/back/models/account.js +++ b/back/models/account.js @@ -4,6 +4,8 @@ module.exports = Self => { require('../methods/account/login')(Self); require('../methods/account/logout')(Self); require('../methods/account/acl')(Self); + require('../methods/account/change-password')(Self); + require('../methods/account/set-password')(Self); require('../methods/account/validate-token')(Self); // Validations diff --git a/back/models/account.json b/back/models/account.json index 7186621b4..fbf736f03 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -24,6 +24,9 @@ "nickname": { "type": "string" }, + "lang": { + "type": "string" + }, "password": { "type": "string", "required": true diff --git a/back/models/language.json b/back/models/language.json new file mode 100644 index 000000000..f4e221464 --- /dev/null +++ b/back/models/language.json @@ -0,0 +1,34 @@ +{ + "name": "Language", + "base": "VnModel", + "options": { + "mysql": { + "table": "hedera.language" + } + }, + "properties": { + "code": { + "type": "string", + "id": true + }, + "name": { + "type": "string", + "required": true + }, + "orgName": { + "type": "string", + "required": true + }, + "isActive": { + "type": "boolean" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/db/changes/10211-accountModule/00-account.sql b/db/changes/10211-accountModule/00-account.sql new file mode 100644 index 000000000..987256793 --- /dev/null +++ b/db/changes/10211-accountModule/00-account.sql @@ -0,0 +1,47 @@ + +ALTER TABLE `account`.`role` + MODIFY COLUMN `hasLogin` tinyint(3) unsigned DEFAULT 1 NOT NULL; + +ALTER TABLE `account`.`roleInherit` + ADD UNIQUE( `role`, `inheritsFrom`); + +ALTER TABLE `account`.`roleInherit` + DROP PRIMARY KEY; + +ALTER TABLE `account`.`roleInherit` + ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + ADD PRIMARY KEY (`id`); + +ALTER TABLE `account`.`mailAlias` + ADD `description` VARCHAR(255) NULL AFTER `alias`; + +ALTER TABLE `account`.`mailAliasAccount` + ADD UNIQUE( `mailAlias`, `account`); + +ALTER TABLE `account`.`mailAliasAccount` + DROP PRIMARY KEY; + +ALTER TABLE `account`.`mailAliasAccount` + ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + ADD PRIMARY KEY (`id`); + + +USE account; + +DELIMITER $$ + +CREATE TRIGGER role_beforeInsert + BEFORE INSERT ON `role` FOR EACH ROW +BEGIN + CALL role_checkName(NEW.`name`); +END$$ + +CREATE TRIGGER role_beforeUpdate + BEFORE UPDATE ON `role` FOR EACH ROW +BEGIN + IF !(NEW.`name` <=> OLD.`name`) THEN + CALL role_checkName (NEW.`name`); + END IF; +END$$ + +DELIMITER ; \ No newline at end of file diff --git a/db/changes/10211-accountModule/00-myUserChangePassword.sql b/db/changes/10211-accountModule/00-myUserChangePassword.sql new file mode 100644 index 000000000..94fc02087 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserChangePassword.sql @@ -0,0 +1,13 @@ +DROP PROCEDURE IF EXISTS account.myUserChangePassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserChangePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * @deprecated Use myUser_changePassword() + */ + CALL myUser_changePassword(vOldPassword, vPassword); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUserChangePassword TO account@localhost; diff --git a/db/changes/10211-accountModule/00-myUserCheckLogin.sql b/db/changes/10211-accountModule/00-myUserCheckLogin.sql new file mode 100644 index 000000000..eaa962b63 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserCheckLogin.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.myUserCheckLogin; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserCheckLogin`() RETURNS tinyint(1) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_checkLogin() + */ + RETURN myUser_checkLogin(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserCheckLogin TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserGetId.sql b/db/changes/10211-accountModule/00-myUserGetId.sql new file mode 100644 index 000000000..f0bb972aa --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserGetId.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.myUserGetId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetId`() RETURNS int(11) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getId() + */ + RETURN myUser_getId(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserGetId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserGetName.sql b/db/changes/10211-accountModule/00-myUserGetName.sql new file mode 100644 index 000000000..2f758d0c6 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserGetName.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.myUserGetName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetName`() RETURNS varchar(30) CHARSET utf8 + NO SQL + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getName() + */ + RETURN myUser_getName(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserGetName TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserHasRole.sql b/db/changes/10211-accountModule/00-myUserHasRole.sql new file mode 100644 index 000000000..6d2301328 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserHasRole.sql @@ -0,0 +1,14 @@ +DROP FUNCTION IF EXISTS account.myUserHasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_hasRole() + */ + RETURN myUser_hasRole(vRoleName); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserHasRole TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUserHasRoleId.sql b/db/changes/10211-accountModule/00-myUserHasRoleId.sql new file mode 100644 index 000000000..380bd0641 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUserHasRoleId.sql @@ -0,0 +1,14 @@ +DROP FUNCTION IF EXISTS account.myUserHasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRoleId`(vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_hasRoleId() + */ + RETURN myUser_hasRoleId(vRoleId); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUserHasRoleId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_changePassword.sql b/db/changes/10211-accountModule/00-myUser_changePassword.sql new file mode 100644 index 000000000..3dd86a881 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_changePassword.sql @@ -0,0 +1,17 @@ +DROP PROCEDURE IF EXISTS account.myUser_changePassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_changePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * Changes the current user password, if user is in recovery mode ignores the + * current password. + * + * @param vOldPassword The current password + * @param vPassword The new password + */ + CALL user_changePassword(myUser_getId(), vOldPassword, vPassword); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_changePassword TO account@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_checkLogin.sql b/db/changes/10211-accountModule/00-myUser_checkLogin.sql new file mode 100644 index 000000000..843f57fff --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_checkLogin.sql @@ -0,0 +1,29 @@ +DROP FUNCTION IF EXISTS account.myUser_checkLogin; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_checkLogin`() RETURNS tinyint(1) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * Checks that variables @userId and @userName haven't been altered. + * + * @return %TRUE if they are unaltered or unset, otherwise %FALSE + */ + DECLARE vSignature VARCHAR(128); + DECLARE vKey VARCHAR(255); + + IF @userId IS NOT NULL + AND @userName IS NOT NULL + AND @userSignature IS NOT NULL + THEN + SELECT loginKey INTO vKey FROM userConfig; + SET vSignature = util.hmacSha2(256, CONCAT_WS('/', @userId, @userName), vKey); + RETURN vSignature = @userSignature; + END IF; + + RETURN FALSE; +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_checkLogin TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_getId.sql b/db/changes/10211-accountModule/00-myUser_getId.sql new file mode 100644 index 000000000..b3d3f1b28 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_getId.sql @@ -0,0 +1,27 @@ +DROP FUNCTION IF EXISTS account.myUser_getId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getId`() RETURNS int(11) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * Returns the current user id. + * + * @return The user id + */ + DECLARE vUser INT DEFAULT NULL; + + IF myUser_checkLogin() + THEN + SET vUser = @userId; + ELSE + SELECT id INTO vUser FROM user + WHERE name = LEFT(USER(), INSTR(USER(), '@') - 1); + END IF; + + RETURN vUser; +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_getId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_getName.sql b/db/changes/10211-accountModule/00-myUser_getName.sql new file mode 100644 index 000000000..b055227d3 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_getName.sql @@ -0,0 +1,27 @@ +DROP FUNCTION IF EXISTS account.myUser_getName; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getName`() RETURNS varchar(30) CHARSET utf8 + NO SQL + DETERMINISTIC +BEGIN +/** + * Returns the current user name. + * + * @return The user name + */ + DECLARE vUser VARCHAR(30) DEFAULT NULL; + + IF myUser_checkLogin() + THEN + SET vUser = @userName; + ELSE + SET vUser = LEFT(USER(), INSTR(USER(), '@') - 1); + END IF; + + RETURN vUser; +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_getName TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_hasRole.sql b/db/changes/10211-accountModule/00-myUser_hasRole.sql new file mode 100644 index 000000000..538b58f08 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_hasRole.sql @@ -0,0 +1,17 @@ +DROP FUNCTION IF EXISTS account.myUser_hasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if current user has/inherits a role. + * + * @param vRoleName Role to check + * @return %TRUE if it has role, %FALSE otherwise + */ + RETURN user_hasRole(myUser_getName(), vRoleName); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_hasRole TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_hasRoleId.sql b/db/changes/10211-accountModule/00-myUser_hasRoleId.sql new file mode 100644 index 000000000..2931443e1 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_hasRoleId.sql @@ -0,0 +1,17 @@ +DROP FUNCTION IF EXISTS account.myUser_hasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRoleId`(vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if current user has/inherits a role. + * + * @param vRoleName Role id to check + * @return %TRUE if it has role, %FALSE otherwise + */ + RETURN user_hasRoleId(myUserGetName(), vRoleId); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.myUser_hasRoleId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_login.sql b/db/changes/10211-accountModule/00-myUser_login.sql new file mode 100644 index 000000000..9d92828b0 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_login.sql @@ -0,0 +1,29 @@ +DROP PROCEDURE IF EXISTS account.myUser_login; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_login`(vUserName VARCHAR(255), vPassword VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * Logs in using the user credentials. + * + * @param vUserName The user name + * @param vPassword The user password + */ + DECLARE vAuthIsOk BOOLEAN DEFAULT FALSE; + + SELECT COUNT(*) = 1 INTO vAuthIsOk FROM user + WHERE name = vUserName + AND password = MD5(vPassword) + AND active; + + IF vAuthIsOk + THEN + CALL myUser_loginWithName (vUserName); + ELSE + CALL util.throw ('INVALID_CREDENTIALS'); + END IF; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_login TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_loginWithKey.sql b/db/changes/10211-accountModule/00-myUser_loginWithKey.sql new file mode 100644 index 000000000..fc12a79d9 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_loginWithKey.sql @@ -0,0 +1,25 @@ +DROP PROCEDURE IF EXISTS account.myUser_loginWithKey; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * Logs in using the user name and MySQL master key. + * + * @param vUserName The user name + * @param vKey The MySQL master key + */ + DECLARE vLoginKey VARCHAR(255); + + SELECT loginKey INTO vLoginKey FROM userConfig; + + IF vLoginKey = vKey THEN + CALL user_loginWithName(vUserName); + ELSE + CALL util.throw('INVALID_KEY'); + END IF; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_loginWithKey TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-myUser_loginWithName.sql b/db/changes/10211-accountModule/00-myUser_loginWithName.sql new file mode 100644 index 000000000..6b86a37f3 --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_loginWithName.sql @@ -0,0 +1,26 @@ +DROP PROCEDURE IF EXISTS account.myUser_loginWithName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithName`(vUserName VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * Logs in using only the user name. This procedure is intended to be executed + * by users with a high level of privileges so that normal users should not have + * execute permissions on it. + * + * @param vUserName The user name + */ + DECLARE vUserId INT DEFAULT NULL; + DECLARE vKey VARCHAR(255); + + SELECT id INTO vUserId FROM user + WHERE name = vUserName; + + SELECT loginKey INTO vKey FROM userConfig; + + SET @userId = vUserId; + SET @userName = vUserName; + SET @userSignature = util.hmacSha2(256, CONCAT_WS('/', vUserId, vUserName), vKey); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-myUser_logout.sql b/db/changes/10211-accountModule/00-myUser_logout.sql new file mode 100644 index 000000000..ffa2c969e --- /dev/null +++ b/db/changes/10211-accountModule/00-myUser_logout.sql @@ -0,0 +1,15 @@ +DROP PROCEDURE IF EXISTS account.myUser_logout; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_logout`() +BEGIN +/** + * Logouts the user. + */ + SET @userId = NULL; + SET @userName = NULL; + SET @userSignature = NULL; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUser_logout TO account@localhost; diff --git a/db/changes/10211-accountModule/00-passwordGenerate.sql b/db/changes/10211-accountModule/00-passwordGenerate.sql new file mode 100644 index 000000000..46048e24d --- /dev/null +++ b/db/changes/10211-accountModule/00-passwordGenerate.sql @@ -0,0 +1,52 @@ +DROP FUNCTION IF EXISTS account.passwordGenerate; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`passwordGenerate`() RETURNS text CHARSET utf8 +BEGIN +/** + * Generates a random password that meets the minimum requirements. + * + * @return Generated password + */ + DECLARE vMinLength TINYINT; + DECLARE vMinAlpha TINYINT; + DECLARE vMinUpper TINYINT; + DECLARE vMinDigits TINYINT; + DECLARE vMinPunct TINYINT; + DECLARE vAlpha TINYINT DEFAULT 0; + DECLARE vUpper TINYINT DEFAULT 0; + DECLARE vDigits TINYINT DEFAULT 0; + DECLARE vPunct TINYINT DEFAULT 0; + DECLARE vRandIndex INT; + DECLARE vPwd TEXT DEFAULT ''; + + DECLARE vAlphaChars TEXT DEFAULT 'abcdefghijklmnopqrstuvwxyz'; + DECLARE vUpperChars TEXT DEFAULT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + DECLARE vDigitChars TEXT DEFAULT '1234567890'; + DECLARE vPunctChars TEXT DEFAULT '!$%&()=.'; + + SELECT length, nAlpha, nUpper, nDigits, nPunct + INTO vMinLength, vMinAlpha, vMinUpper, vMinDigits, vMinPunct FROM userPassword; + + WHILE LENGTH(vPwd) < vMinLength OR vAlpha < vMinAlpha + OR vUpper < vMinUpper OR vDigits < vMinDigits OR vPunct < vMinPunct DO + SET vRandIndex = FLOOR((RAND() * 4) + 1); + + CASE + WHEN vRandIndex = 1 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vAlphaChars, FLOOR((RAND() * 26) + 1), 1)); + SET vAlpha = vAlpha + 1; + WHEN vRandIndex = 2 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vUpperChars, FLOOR((RAND() * 26) + 1), 1)); + SET vUpper = vUpper + 1; + WHEN vRandIndex = 3 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vDigitChars, FLOOR((RAND() * 10) + 1), 1)); + SET vDigits = vDigits + 1; + WHEN vRandIndex = 4 THEN + SET vPwd = CONCAT(vPwd, SUBSTRING(vPunctChars, FLOOR((RAND() * LENGTH(vPunctChars)) + 1), 1)); + SET vPunct = vPunct + 1; + END CASE; + END WHILE; + RETURN vPwd; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_checkName.sql b/db/changes/10211-accountModule/00-role_checkName.sql new file mode 100644 index 000000000..1e4f31767 --- /dev/null +++ b/db/changes/10211-accountModule/00-role_checkName.sql @@ -0,0 +1,18 @@ +DROP PROCEDURE IF EXISTS account.role_checkName; + +DELIMITER $$ +CREATE PROCEDURE account.role_checkName(vRoleName VARCHAR(255)) +BEGIN +/** + * Checks that role name meets the necessary syntax requirements, otherwise it + * throws an exception. + * Role name must be written in camelCase. + * + * @param vRoleName The role name + */ + IF BINARY vRoleName NOT REGEXP '^[a-z][a-zA-Z]+$' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Role name must be written in camelCase'; + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_getDescendents.sql b/db/changes/10211-accountModule/00-role_getDescendents.sql new file mode 100644 index 000000000..9b224f6eb --- /dev/null +++ b/db/changes/10211-accountModule/00-role_getDescendents.sql @@ -0,0 +1,66 @@ +DROP PROCEDURE IF EXISTS account.role_getDescendents; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_getDescendents`(vSelf INT) +BEGIN +/** + * Gets the identifiers of all the subroles implemented by a role (Including + * itself). + * + * @param vSelf The role identifier + * @table tmp.role Subroles implemented by the role + */ + DECLARE vIsRoot BOOL; + + DROP TEMPORARY TABLE IF EXISTS + tmp.role, parents, childs; + + CREATE TEMPORARY TABLE tmp.role + (UNIQUE (id)) + ENGINE = MEMORY + SELECT vSelf AS id; + + CREATE TEMPORARY TABLE parents + ENGINE = MEMORY + SELECT vSelf AS id; + + CREATE TEMPORARY TABLE childs + LIKE parents; + + REPEAT + DELETE FROM childs; + INSERT INTO childs + SELECT DISTINCT r.inheritsFrom id + FROM parents p + JOIN roleInherit r ON r.role = p.id + LEFT JOIN tmp.role t ON t.id = r.inheritsFrom + WHERE t.id IS NULL; + + DELETE FROM parents; + INSERT INTO parents + SELECT * FROM childs; + + INSERT INTO tmp.role + SELECT * FROM childs; + + UNTIL ROW_COUNT() <= 0 + END REPEAT; + + -- If it is root all the roles are added + + SELECT COUNT(*) > 0 INTO vIsRoot + FROM tmp.role t + JOIN role r ON r.id = t.id + WHERE r.`name` = 'root'; + + IF vIsRoot THEN + INSERT IGNORE INTO tmp.role (id) + SELECT id FROM role; + END IF; + + -- Cleaning + + DROP TEMPORARY TABLE + parents, childs; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_sync.sql b/db/changes/10211-accountModule/00-role_sync.sql new file mode 100644 index 000000000..8e16ef567 --- /dev/null +++ b/db/changes/10211-accountModule/00-role_sync.sql @@ -0,0 +1,53 @@ +DROP PROCEDURE IF EXISTS account.role_sync; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_sync`() +BEGIN +/** + * Synchronize the @roleRole table with the current role hierarchy. This + * procedure must be called every time the @roleInherit table is modified so + * that the changes made on it are effective. + */ + DECLARE vRoleId INT; + DECLARE vDone BOOL; + + DECLARE cur CURSOR FOR + SELECT id FROM role; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + DROP TEMPORARY TABLE IF EXISTS tRoleRole; + CREATE TEMPORARY TABLE tRoleRole + ENGINE = MEMORY + SELECT * FROM roleRole LIMIT 0; + + OPEN cur; + + l: LOOP + SET vDone = FALSE; + FETCH cur INTO vRoleId; + + IF vDone THEN + LEAVE l; + END IF; + + CALL role_getDescendents(vRoleId); + + INSERT INTO tRoleRole (role, inheritsFrom) + SELECT vRoleId, id FROM tmp.role; + + DROP TEMPORARY TABLE tmp.role; + END LOOP; + + CLOSE cur; + + START TRANSACTION; + DELETE FROM roleRole; + INSERT INTO roleRole SELECT * FROM tRoleRole; + COMMIT; + + DROP TEMPORARY TABLE tRoleRole; + + CALL role_syncPrivileges; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-role_syncPrivileges.sql b/db/changes/10211-accountModule/00-role_syncPrivileges.sql new file mode 100644 index 000000000..0d6d8975b --- /dev/null +++ b/db/changes/10211-accountModule/00-role_syncPrivileges.sql @@ -0,0 +1,494 @@ +DROP PROCEDURE IF EXISTS account.role_syncPrivileges; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_syncPrivileges`() +BEGIN +/** + * Synchronizes permissions of MySQL role users based on role hierarchy. + * The computed role users of permission mix will be named according to + * pattern z-[role_name]. + * + * If any@localhost user exists, it will be taken as a template for basic + * attributes. + * + * Warning! This procedure should only be called when MySQL privileges + * are modified. If role hierarchy is modified, you must call the role_sync() + * procedure wich calls this internally. + */ + DECLARE vIsMysql BOOL DEFAULT VERSION() NOT LIKE '%MariaDB%'; + DECLARE vVersion INT DEFAULT SUBSTRING_INDEX(VERSION(), '.', 1); + DECLARE vTplUser VARCHAR(255) DEFAULT 'any'; + DECLARE vTplHost VARCHAR(255) DEFAULT '%'; + DECLARE vRoleHost VARCHAR(255) DEFAULT 'localhost'; + DECLARE vAllHost VARCHAR(255) DEFAULT '%'; + DECLARE vPrefix VARCHAR(2) DEFAULT 'z-'; + DECLARE vPrefixedLike VARCHAR(255); + DECLARE vPassword VARCHAR(255) DEFAULT ''; + + -- Deletes computed role users + + SET vPrefixedLike = CONCAT(vPrefix, '%'); + + DELETE FROM mysql.user + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.db + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.tables_priv + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.columns_priv + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.procs_priv + WHERE `User` LIKE vPrefixedLike; + + DELETE FROM mysql.proxies_priv + WHERE `Proxied_user` LIKE vPrefixedLike; + + -- Temporary tables + + DROP TEMPORARY TABLE IF EXISTS tRole; + CREATE TEMPORARY TABLE tRole + (INDEX (id)) + ENGINE = MEMORY + SELECT + id, + `name` role, + CONCAT(vPrefix, `name`) prefixedRole + FROM role + WHERE hasLogin; + + DROP TEMPORARY TABLE IF EXISTS tRoleInherit; + CREATE TEMPORARY TABLE tRoleInherit + (INDEX (inheritsFrom)) + ENGINE = MEMORY + SELECT + r.prefixedRole, + ri.`name` inheritsFrom + FROM tRole r + JOIN roleRole rr ON rr.role = r.id + JOIN role ri ON ri.id = rr.inheritsFrom; + + -- Recreate role users + + IF vIsMysql THEN + DROP TEMPORARY TABLE IF EXISTS tUser; + CREATE TEMPORARY TABLE tUser + SELECT + r.prefixedRole `User`, + vTplHost `Host`, + IFNULL(t.`authentication_string`, + '') `authentication_string`, + IFNULL(t.`plugin`, + 'mysql_native_password') `plugin`, + IFNULL(IF('' != u.`ssl_type`, + u.`ssl_type`, t.`ssl_type`), + '') `ssl_type`, + IFNULL(IF('' != u.`ssl_cipher`, + u.`ssl_cipher`, t.`ssl_cipher`), + '') `ssl_cipher`, + IFNULL(IF('' != u.`x509_issuer`, + u.`x509_issuer`, t.`x509_issuer`), + '') `x509_issuer`, + IFNULL(IF('' != u.`x509_subject`, + u.`x509_subject`, t.`x509_subject`), + '') `x509_subject`, + IFNULL(IF(0 != u.`max_questions`, + u.`max_questions`, t.`max_questions`), + 0) `max_questions`, + IFNULL(IF(0 != u.`max_updates`, + u.`max_updates`, t.`max_updates`), + 0) `max_updates`, + IFNULL(IF(0 != u.`max_connections`, + u.`max_connections`, t.`max_connections`), + 0) `max_connections`, + IFNULL(IF(0 != u.`max_user_connections`, + u.`max_user_connections`, t.`max_user_connections`), + 0) `max_user_connections` + FROM tRole r + LEFT JOIN mysql.user t + ON t.`User` = vTplUser + AND t.`Host` = vRoleHost + LEFT JOIN mysql.user u + ON u.`User` = r.role + AND u.`Host` = vRoleHost; + + IF vVersion <= 5 THEN + SELECT `Password` INTO vPassword + FROM mysql.user + WHERE `User` = vTplUser + AND `Host` = vRoleHost; + + INSERT INTO mysql.user ( + `User`, + `Host`, + `Password`, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + ) + SELECT + `User`, + `Host`, + vPassword, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + FROM tUser; + ELSE + INSERT INTO mysql.user ( + `User`, + `Host`, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + ) + SELECT + `User`, + `Host`, + `authentication_string`, + `plugin`, + `ssl_type`, + `ssl_cipher`, + `x509_issuer`, + `x509_subject`, + `max_questions`, + `max_updates`, + `max_connections`, + `max_user_connections` + FROM tUser; + END IF; + + DROP TEMPORARY TABLE IF EXISTS tUser; + ELSE + INSERT INTO mysql.global_priv ( + `User`, + `Host`, + `Priv` + ) + SELECT + r.prefixedRole, + vTplHost, + JSON_MERGE_PATCH( + IFNULL(t.`Priv`, '{}'), + IFNULL(u.`Priv`, '{}') + ) + FROM tRole r + LEFT JOIN mysql.global_priv t + ON t.`User` = vTplUser + AND t.`Host` = vRoleHost + LEFT JOIN mysql.global_priv u + ON u.`User` = r.role + AND u.`Host` = vRoleHost; + END IF; + + INSERT INTO mysql.proxies_priv ( + `User`, + `Host`, + `Proxied_user`, + `Proxied_host`, + `Grantor` + ) + SELECT + '', + vAllHost, + prefixedRole, + vTplHost, + CONCAT(prefixedRole, '@', vTplHost) + FROM tRole; + + -- Copies global privileges + + DROP TEMPORARY TABLE IF EXISTS tUserPriv; + + IF vIsMysql THEN + CREATE TEMPORARY TABLE tUserPriv + (INDEX (prefixedRole)) + ENGINE = MEMORY + SELECT + r.prefixedRole, + MAX(u.`Select_priv`) `Select_priv`, + MAX(u.`Insert_priv`) `Insert_priv`, + MAX(u.`Update_priv`) `Update_priv`, + MAX(u.`Delete_priv`) `Delete_priv`, + MAX(u.`Create_priv`) `Create_priv`, + MAX(u.`Drop_priv`) `Drop_priv`, + MAX(u.`Reload_priv`) `Reload_priv`, + MAX(u.`Shutdown_priv`) `Shutdown_priv`, + MAX(u.`Process_priv`) `Process_priv`, + MAX(u.`File_priv`) `File_priv`, + MAX(u.`Grant_priv`) `Grant_priv`, + MAX(u.`References_priv`) `References_priv`, + MAX(u.`Index_priv`) `Index_priv`, + MAX(u.`Alter_priv`) `Alter_priv`, + MAX(u.`Show_db_priv`) `Show_db_priv`, + MAX(u.`Super_priv`) `Super_priv`, + MAX(u.`Create_tmp_table_priv`) `Create_tmp_table_priv`, + MAX(u.`Lock_tables_priv`) `Lock_tables_priv`, + MAX(u.`Execute_priv`) `Execute_priv`, + MAX(u.`Repl_slave_priv`) `Repl_slave_priv`, + MAX(u.`Repl_client_priv`) `Repl_client_priv`, + MAX(u.`Create_view_priv`) `Create_view_priv`, + MAX(u.`Show_view_priv`) `Show_view_priv`, + MAX(u.`Create_routine_priv`) `Create_routine_priv`, + MAX(u.`Alter_routine_priv`) `Alter_routine_priv`, + MAX(u.`Create_user_priv`) `Create_user_priv`, + MAX(u.`Event_priv`) `Event_priv`, + MAX(u.`Trigger_priv`) `Trigger_priv`, + MAX(u.`Create_tablespace_priv`) `Create_tablespace_priv` + FROM tRoleInherit r + JOIN mysql.user u + ON u.`User` = r.inheritsFrom + AND u.`Host`= vRoleHost + GROUP BY r.prefixedRole; + + UPDATE mysql.user u + JOIN tUserPriv t + ON u.`User` = t.prefixedRole + AND u.`Host` = vTplHost + SET + u.`Select_priv` + = t.`Select_priv`, + u.`Insert_priv` + = t.`Insert_priv`, + u.`Update_priv` + = t.`Update_priv`, + u.`Delete_priv` + = t.`Delete_priv`, + u.`Create_priv` + = t.`Create_priv`, + u.`Drop_priv` + = t.`Drop_priv`, + u.`Reload_priv` + = t.`Reload_priv`, + u.`Shutdown_priv` + = t.`Shutdown_priv`, + u.`Process_priv` + = t.`Process_priv`, + u.`File_priv` + = t.`File_priv`, + u.`Grant_priv` + = t.`Grant_priv`, + u.`References_priv` + = t.`References_priv`, + u.`Index_priv` + = t.`Index_priv`, + u.`Alter_priv` + = t.`Alter_priv`, + u.`Show_db_priv` + = t.`Show_db_priv`, + u.`Super_priv` + = t.`Super_priv`, + u.`Create_tmp_table_priv` + = t.`Create_tmp_table_priv`, + u.`Lock_tables_priv` + = t.`Lock_tables_priv`, + u.`Execute_priv` + = t.`Execute_priv`, + u.`Repl_slave_priv` + = t.`Repl_slave_priv`, + u.`Repl_client_priv` + = t.`Repl_client_priv`, + u.`Create_view_priv` + = t.`Create_view_priv`, + u.`Show_view_priv` + = t.`Show_view_priv`, + u.`Create_routine_priv` + = t.`Create_routine_priv`, + u.`Alter_routine_priv` + = t.`Alter_routine_priv`, + u.`Create_user_priv` + = t.`Create_user_priv`, + u.`Event_priv` + = t.`Event_priv`, + u.`Trigger_priv` + = t.`Trigger_priv`, + u.`Create_tablespace_priv` + = t.`Create_tablespace_priv`; + ELSE + CREATE TEMPORARY TABLE tUserPriv + (INDEX (prefixedRole)) + SELECT + r.prefixedRole, + BIT_OR(JSON_VALUE(p.`Priv`, '$.access')) access + FROM tRoleInherit r + JOIN mysql.global_priv p + ON p.`User` = r.inheritsFrom + AND p.`Host`= vRoleHost + GROUP BY r.prefixedRole; + + UPDATE mysql.global_priv p + JOIN tUserPriv t + ON p.`User` = t.prefixedRole + AND p.`Host` = vTplHost + SET + p.`Priv` = JSON_SET(p.`Priv`, '$.access', t.access); + END IF; + + DROP TEMPORARY TABLE tUserPriv; + + -- Copy schema level privileges + + INSERT INTO mysql.db ( + `User`, + `Host`, + `Db`, + `Select_priv`, + `Insert_priv`, + `Update_priv`, + `Delete_priv`, + `Create_priv`, + `Drop_priv`, + `Grant_priv`, + `References_priv`, + `Index_priv`, + `Alter_priv`, + `Create_tmp_table_priv`, + `Lock_tables_priv`, + `Create_view_priv`, + `Show_view_priv`, + `Create_routine_priv`, + `Alter_routine_priv`, + `Execute_priv`, + `Event_priv`, + `Trigger_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + MAX(t.`Select_priv`), + MAX(t.`Insert_priv`), + MAX(t.`Update_priv`), + MAX(t.`Delete_priv`), + MAX(t.`Create_priv`), + MAX(t.`Drop_priv`), + MAX(t.`Grant_priv`), + MAX(t.`References_priv`), + MAX(t.`Index_priv`), + MAX(t.`Alter_priv`), + MAX(t.`Create_tmp_table_priv`), + MAX(t.`Lock_tables_priv`), + MAX(t.`Create_view_priv`), + MAX(t.`Show_view_priv`), + MAX(t.`Create_routine_priv`), + MAX(t.`Alter_routine_priv`), + MAX(t.`Execute_priv`), + MAX(t.`Event_priv`), + MAX(t.`Trigger_priv`) + FROM tRoleInherit r + JOIN mysql.db t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost + GROUP BY r.prefixedRole, t.`Db`; + + -- Copy table level privileges + + INSERT INTO mysql.tables_priv ( + `User`, + `Host`, + `Db`, + `Table_name`, + `Grantor`, + `Timestamp`, + `Table_priv`, + `Column_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + t.`Table_name`, + t.`Grantor`, + MAX(t.`Timestamp`), + IFNULL(GROUP_CONCAT(NULLIF(t.`Table_priv`, '')), ''), + IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '') + FROM tRoleInherit r + JOIN mysql.tables_priv t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost + GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`; + + -- Copy column level privileges + + INSERT INTO mysql.columns_priv ( + `User`, + `Host`, + `Db`, + `Table_name`, + `Column_name`, + `Timestamp`, + `Column_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + t.`Table_name`, + t.`Column_name`, + MAX(t.`Timestamp`), + IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '') + FROM tRoleInherit r + JOIN mysql.columns_priv t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost + GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`, t.`Column_name`; + + -- Copy routine privileges + + INSERT IGNORE INTO mysql.procs_priv ( + `User`, + `Host`, + `Db`, + `Routine_name`, + `Routine_type`, + `Grantor`, + `Timestamp`, + `Proc_priv` + ) + SELECT + r.prefixedRole, + vTplHost, + t.`Db`, + t.`Routine_name`, + t.`Routine_type`, + t.`Grantor`, + t.`Timestamp`, + t.`Proc_priv` + FROM tRoleInherit r + JOIN mysql.procs_priv t + ON t.`User` = r.inheritsFrom + AND t.`Host`= vRoleHost; + + -- Free memory + + DROP TEMPORARY TABLE + tRole, + tRoleInherit; + + FLUSH PRIVILEGES; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userGetId.sql b/db/changes/10211-accountModule/00-userGetId.sql new file mode 100644 index 000000000..219ea680c --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetId.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.userGetId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetId`() RETURNS int(11) + READS SQL DATA + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getId() + */ + RETURN myUser_getId(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.userGetId TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userGetMysqlRole.sql b/db/changes/10211-accountModule/00-userGetMysqlRole.sql new file mode 100644 index 000000000..673f1aac9 --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetMysqlRole.sql @@ -0,0 +1,11 @@ +DROP FUNCTION IF EXISTS account.userGetMysqlRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8 +BEGIN +/** + * @deprecated Use user_getMysqlRole() + */ + RETURN user_getMysqlRole(); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userGetName.sql b/db/changes/10211-accountModule/00-userGetName.sql new file mode 100644 index 000000000..f49f2dbef --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetName.sql @@ -0,0 +1,15 @@ +DROP FUNCTION IF EXISTS account.userGetName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetName`() RETURNS varchar(30) CHARSET utf8 + NO SQL + DETERMINISTIC +BEGIN +/** + * @deprecated Use myUser_getName() + */ + RETURN myUser_getName(); +END$$ +DELIMITER ; + +GRANT EXECUTE ON FUNCTION account.userGetName TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userGetNameFromId.sql b/db/changes/10211-accountModule/00-userGetNameFromId.sql new file mode 100644 index 000000000..f8e9333cb --- /dev/null +++ b/db/changes/10211-accountModule/00-userGetNameFromId.sql @@ -0,0 +1,11 @@ +DROP FUNCTION IF EXISTS account.userGetNameFromId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8 +BEGIN +/** + * @deprecated Use user_getNameFromId(); + */ + RETURN user_getNameFromId(vSelf); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userHasRole.sql b/db/changes/10211-accountModule/00-userHasRole.sql new file mode 100644 index 000000000..3e09d27bf --- /dev/null +++ b/db/changes/10211-accountModule/00-userHasRole.sql @@ -0,0 +1,12 @@ +DROP FUNCTION IF EXISTS account.userHasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use user_hasRole() + */ + RETURN user_hasRole(vUserName, vRoleName); +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/10211-accountModule/00-userHasRoleId.sql b/db/changes/10211-accountModule/00-userHasRoleId.sql new file mode 100644 index 000000000..9fcd9f073 --- /dev/null +++ b/db/changes/10211-accountModule/00-userHasRoleId.sql @@ -0,0 +1,12 @@ +DROP FUNCTION IF EXISTS account.userHasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * @deprecated Use user_hasRoleId() + */ + RETURN user_hasRoleId(vUser, vRoleId); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userLogin.sql b/db/changes/10211-accountModule/00-userLogin.sql new file mode 100644 index 000000000..63a332254 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLogin.sql @@ -0,0 +1,14 @@ +DROP PROCEDURE IF EXISTS account.userLogin; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLogin`(vUserName VARCHAR(255), vPassword VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * @deprecated Use myUser_login() + */ + CALL myUser_login(vUserName, vPassword); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.userLogin TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userLoginWithKey.sql b/db/changes/10211-accountModule/00-userLoginWithKey.sql new file mode 100644 index 000000000..45a490c71 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLoginWithKey.sql @@ -0,0 +1,14 @@ +DROP PROCEDURE IF EXISTS account.userLoginWithKey; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * @deprecated Use myUser_loginWithKey() + */ + CALL myUser_loginWithKey(vUserName, vKey); +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.userLoginWithKey TO guest@localhost; diff --git a/db/changes/10211-accountModule/00-userLoginWithName.sql b/db/changes/10211-accountModule/00-userLoginWithName.sql new file mode 100644 index 000000000..4053970e4 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLoginWithName.sql @@ -0,0 +1,12 @@ +DROP PROCEDURE IF EXISTS account.userLoginWithName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithName`(vUserName VARCHAR(255)) + READS SQL DATA +BEGIN +/** + * @deprecated Use myUser_loginWithName() + */ + CALL myUser_loginWithName(vUserName); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-userLogout.sql b/db/changes/10211-accountModule/00-userLogout.sql new file mode 100644 index 000000000..7d0d68324 --- /dev/null +++ b/db/changes/10211-accountModule/00-userLogout.sql @@ -0,0 +1,14 @@ +DROP PROCEDURE IF EXISTS account.myUserLogout; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserLogout`() +BEGIN +/** + * @deprecated Use myUser_Logout() + */ + CALL myUser_logout; +END$$ +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE account.myUserLogout TO account@localhost; diff --git a/db/changes/10211-accountModule/00-userSetPassword.sql b/db/changes/10211-accountModule/00-userSetPassword.sql new file mode 100644 index 000000000..fd3daec53 --- /dev/null +++ b/db/changes/10211-accountModule/00-userSetPassword.sql @@ -0,0 +1,16 @@ +DROP PROCEDURE IF EXISTS account.userSetPassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userSetPassword`(vUserName VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * @deprecated Use user_setPassword() + */ + DECLARE vUserId INT; + + SELECT id INTO vUserId + FROM user WHERE `name` = vUserName; + + CALL user_setPassword(vUserId, vPassword); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_changePassword.sql b/db/changes/10211-accountModule/00-user_changePassword.sql new file mode 100644 index 000000000..c137213e0 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_changePassword.sql @@ -0,0 +1,27 @@ +DROP PROCEDURE IF EXISTS account.user_changePassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE account.user_changePassword(vSelf INT, vOldPassword VARCHAR(255), vPassword VARCHAR(255)) +BEGIN +/** + * Changes the user password. + * + * @param vSelf The user id + * @param vOldPassword The current password + * @param vPassword The new password + */ + DECLARE vPasswordOk BOOL; + DECLARE vUserName VARCHAR(255); + + SELECT `password` = MD5(vOldPassword), `name` + INTO vPasswordOk, vUserName + FROM user WHERE id = vSelf; + + IF NOT vPasswordOk THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Invalid password'; + END IF; + + CALL user_setPassword(vSelf, vPassword); +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_checkName.sql b/db/changes/10211-accountModule/00-user_checkName.sql new file mode 100644 index 000000000..9b54d6175 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_checkName.sql @@ -0,0 +1,17 @@ +DROP PROCEDURE IF EXISTS account.user_checkName; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE `account`.`user_checkName`(vUserName VARCHAR(255)) +BEGIN +/** + * Checks that username meets the necessary syntax requirements, otherwise it + * throws an exception. + * The user name must only contain lowercase letters or, starting with second + * character, numbers or underscores. + */ + IF vUserName NOT REGEXP '^[a-z0-9_-]*$' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'INVALID_USER_NAME'; + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_getMysqlRole.sql b/db/changes/10211-accountModule/00-user_getMysqlRole.sql new file mode 100644 index 000000000..4088ea8a4 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_getMysqlRole.sql @@ -0,0 +1,22 @@ +DROP FUNCTION IF EXISTS account.user_getMysqlRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8 +BEGIN +/** + * From a username, it returns the associated MySQL wich should be used when + * using external authentication systems. + * + * @param vUserName The user name + * @return The associated MySQL role + */ + DECLARE vRole VARCHAR(255); + + SELECT CONCAT(IF(r.hasLogin, 'z-', ''), r.name) INTO vRole + FROM role r + JOIN user u ON u.role = r.id + WHERE u.name = vUserName; + + RETURN vRole; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_getNameFromId.sql b/db/changes/10211-accountModule/00-user_getNameFromId.sql new file mode 100644 index 000000000..ae9ae5941 --- /dev/null +++ b/db/changes/10211-accountModule/00-user_getNameFromId.sql @@ -0,0 +1,20 @@ +DROP FUNCTION IF EXISTS account.user_getNameFromId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8 +BEGIN +/** + * Gets user name from it's id. + * + * @param vSelf The user id + * @return The user name + */ + DECLARE vName VARCHAR(30); + + SELECT `name` INTO vName + FROM user + WHERE id = vId; + + RETURN vSelf; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_hasRole.sql b/db/changes/10211-accountModule/00-user_hasRole.sql new file mode 100644 index 000000000..d42c81deb --- /dev/null +++ b/db/changes/10211-accountModule/00-user_hasRole.sql @@ -0,0 +1,25 @@ +DROP FUNCTION IF EXISTS account.user_hasRole; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if user has/inherits a role. + * + * @param vUserName The user name + * @param vRoleName Role to check + * @return %TRUE if it has role, %FALSE otherwise + */ + DECLARE vHasRole BOOL DEFAULT FALSE; + + SELECT COUNT(*) > 0 INTO vHasRole + FROM user u + JOIN roleRole rr ON rr.role = u.role + JOIN role r ON r.id = rr.inheritsFrom + WHERE u.`name` = vUserName + AND r.`name` = vRoleName COLLATE 'utf8_unicode_ci'; + + RETURN vHasRole; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_hasRoleId.sql b/db/changes/10211-accountModule/00-user_hasRoleId.sql new file mode 100644 index 000000000..b2f523e8c --- /dev/null +++ b/db/changes/10211-accountModule/00-user_hasRoleId.sql @@ -0,0 +1,25 @@ +DROP FUNCTION IF EXISTS account.user_hasRoleId; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Checks if user has/inherits a role. + * + * @param vUserName The user name + * @param vRoleId Role id to check + * @return %TRUE if it has role, %FALSE otherwise + */ + DECLARE vHasRole BOOL DEFAULT FALSE; + + SELECT COUNT(*) > 0 INTO vHasRole + FROM user u + JOIN roleRole rr ON rr.role = u.role + JOIN role r ON r.id = rr.inheritsFrom + WHERE u.`name` = vUser + AND r.id = vRoleId; + + RETURN vHasRole; +END$$ +DELIMITER ; diff --git a/db/changes/10211-accountModule/00-user_setPassword.sql b/db/changes/10211-accountModule/00-user_setPassword.sql new file mode 100644 index 000000000..430c60eab --- /dev/null +++ b/db/changes/10211-accountModule/00-user_setPassword.sql @@ -0,0 +1,23 @@ +DROP PROCEDURE IF EXISTS account.user_setPassword; + +DELIMITER $$ +CREATE DEFINER=`root`@`%` PROCEDURE account.user_setPassword(vSelf INT, vPassword VARCHAR(255)) +BEGIN +/** + * Change the password of the passed as a parameter. Only administrators should + * have execute privileges on the procedure since it does not request the user's + * current password. + * + * @param vSelf The user id + * @param vPassword New password + */ + CALL user_checkPassword(vPassword); + + UPDATE user SET + `password` = MD5(vPassword), + `recoverPass` = FALSE + WHERE id = vSelf; + + CALL user_syncPassword(vSelf, vPassword); +END$$ +DELIMITER ; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 56f779e55..d339506db 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -12,10 +12,6 @@ INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) VALUES ('1', '6'); -INSERT INTO `account`.`mailConfig` (`id`, `domain`) - VALUES - ('1', 'verdnatura.es'); - INSERT INTO `vn`.`bionicConfig` (`generalInflationCoeficient`, `minimumDensityVolumetricWeight`, `verdnaturaVolumeBox`, `itemCarryBox`) VALUES (1.30, 167.00, 138000, 71); @@ -33,6 +29,7 @@ INSERT INTO `vn`.`packagingConfig`(`upperGap`) ('10'); UPDATE `account`.`role` SET id = 100 WHERE id = 0; +CALL `account`.`role_sync`; INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`) SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en' @@ -52,7 +49,7 @@ DELETE FROM `vn`.`worker` WHERE firstName ='customer'; INSERT INTO `hedera`.`tpvConfig`(`id`, `currency`, `terminal`, `transactionType`, `maxAmount`, `employeeFk`, `testUrl`) VALUES (1, 978, 1, 0, 2000, 9, 0); - + INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`) VALUES (101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'), @@ -68,6 +65,36 @@ INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`, (111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'), (112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'); + +INSERT INTO `account`.`userPassword` (`id`, `length`, `nAlpha`, `nUpper`, `nDigits`, `nPunct`) + VALUES + (1, 8, 1, 1, 1, 1); + +INSERT INTO `account`.`account`(`id`) + VALUES + (101), + (102); + +INSERT INTO `account`.`mailConfig` (`id`, `domain`) + VALUES + (1, 'verdnatura.es'); + +INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`) + VALUES + (1, 'general', 'General mailing list', FALSE), + (2, 'it' , 'IT department' , TRUE), + (3, 'sales' , 'Sales department' , TRUE); + +INSERT INTO `account`.`mailAliasAccount`(`mailAlias`, `account`) + VALUES + (1, 101), + (1, 102), + (2, 101); + +INSERT INTO `account`.`mailForward`(`account`, `forwardTo`) + VALUES + (101, 'employee@domain.local'); + INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`) VALUES (106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106), @@ -88,6 +115,15 @@ INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, (19,'Francia', 1, 'FR', 1, 27), (30,'Canarias', 1, 'IC', 1, 24); +INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`) + VALUES + ('ca', 'Català' , 'Catalan' , TRUE), + ('en', 'English' , 'English' , TRUE), + ('es', 'Español' , 'Spanish' , TRUE), + ('fr', 'Français' , 'French' , TRUE), + ('mn', 'Португалий', 'Mongolian' , TRUE), + ('pt', 'Português' , 'Portuguese', TRUE); + INSERT INTO `vn`.`warehouseAlias`(`id`, `name`) VALUES (1, 'Main Warehouse'), diff --git a/front/core/components/crud-model/crud-model.js b/front/core/components/crud-model/crud-model.js index 9cfa3b410..4994e1547 100644 --- a/front/core/components/crud-model/crud-model.js +++ b/front/core/components/crud-model/crud-model.js @@ -134,7 +134,7 @@ export default class CrudModel extends ModelProxy { */ save() { if (!this.isChanged) - return null; + return this.$q.resolve(); let deletes = []; let updates = []; diff --git a/front/core/components/data-viewer/index.html b/front/core/components/data-viewer/index.html index 8c843d869..3488b7b0b 100644 --- a/front/core/components/data-viewer/index.html +++ b/front/core/components/data-viewer/index.html @@ -1,6 +1,7 @@