From 459f4068abe2514fab06b449b5bb548492b89983 Mon Sep 17 00:00:00 2001 From: bernat Date: Sat, 31 Oct 2020 11:05:16 +0100 Subject: [PATCH 1/3] fix fixtures --- db/dump/fixtures.sql | 84 ++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 6bf5ee8fe0..ef3355562d 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -292,48 +292,48 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`) INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`) VALUES - (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 1), - (2, 'Petter Parker', '20 Ingram Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 1), - (3, 'Clark Kent', '344 Clinton Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 1), - (4, 'Tony Stark', '10880 Malibu Point', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 1), - (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 1), - (6, 'DavidCharlesHaller', 'Evil hideout', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 1), - (7, 'Hank Pym', 'Anthill', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 1), - (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 1), - (9, 'Bruce Banner', 'Somewhere in New York', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 1), - (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 1), - (11, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 111, 10, NULL, NULL, 0, 1), - (12, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 112, 10, NULL, NULL, 0, 1), - (101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), - (103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), - (104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), - (105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), - (106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), - (107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), - (108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), - (109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (118, 'address 18', 'Somewhere over the rainbow', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), - (121, 'address 21', 'the bat cave', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 0), - (122, 'address 22', 'NY roofs', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 0), - (123, 'address 23', 'The phone box', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 0), - (124, 'address 24', 'Stark tower Silla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 0), - (125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 0), - (126, 'address 26', 'Many places', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 0), - (127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 0), - (128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 0), - (129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 0), - (130, 'address 30', 'Non valid address', 'Silla', 46460, 1, 1111111111, 222222222, 0, 101, 2, NULL, NULL, 0, 0); + (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 1), + (2, 'Petter Parker', '20 Ingram Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 1), + (3, 'Clark Kent', '344 Clinton Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 1), + (4, 'Tony Stark', '10880 Malibu Point', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 1), + (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 1), + (6, 'DavidCharlesHaller', 'Evil hideout', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 1), + (7, 'Hank Pym', 'Anthill', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 1), + (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 1), + (9, 'Bruce Banner', 'Somewhere in New York', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 1), + (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 1), + (11, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 111, 10, NULL, NULL, 0, 1), + (12, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 112, 10, NULL, NULL, 0, 1), + (101, 'Somewhere in Thailand', 'address 01', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (102, 'Somewhere in Poland', 'address 02', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), + (103, 'Somewhere in Japan', 'address 03', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), + (104, 'Somewhere in Spain', 'address 04', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), + (105, 'Somewhere in Potugal', 'address 05', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), + (106, 'Somewhere in UK', 'address 06', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), + (107, 'Somewhere in Valencia', 'address 07', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), + (108, 'Somewhere in Silla', 'address 08', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0), + (109, 'Somewhere in London', 'address 09', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (110, 'Somewhere in Algemesi', 'address 10', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (111, 'Somewhere in Carlet', 'address 11', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (112, 'Somewhere in Campanar', 'address 12', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (113, 'Somewhere in Malilla', 'address 13', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (114, 'Somewhere in France', 'address 14', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (115, 'Somewhere in Birmingham', 'address 15', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (116, 'Somewhere in Scotland', 'address 16', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (117, 'Somewhere in nowhere', 'address 17', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (118, 'Somewhere over the rainbow', 'address 18', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (119, 'Somewhere in Alberic', 'address 19', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (120, 'Somewhere in Montortal', 'address 20', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), + (121, 'the bat cave', 'address 21', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 0), + (122, 'NY roofs', 'address 22', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 0), + (123, 'The phone box', 'address 23', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 0), + (124, 'Stark tower Silla', 'address 24', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 0), + (125, 'The plastic cell', 'address 25', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 0), + (126, 'Many places', 'address 26', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 0), + (127, 'Your pocket', 'address 27', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 0), + (128, 'Cerebro', 'address 28', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 0), + (129, 'Luke Cages Bar', 'address 29', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 0), + (130, 'Non valid address', 'address 30', 'Silla', 46460, 1, 1111111111, 222222222, 0, 101, 2, NULL, NULL, 0, 0); INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`) SELECT name, CONCAT(name, 'Street'), 'SILLA', 46460, 1, 1, id, 2, 1 From e68453c0478acd1e6684b2b7f468fc7c9655d6ae Mon Sep 17 00:00:00 2001 From: bernat Date: Mon, 2 Nov 2020 08:26:53 +0100 Subject: [PATCH 2/3] fix test --- e2e/paths/02-client/05_add_address.spec.js | 6 +++--- e2e/paths/07-order/01_summary.spec.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/paths/02-client/05_add_address.spec.js b/e2e/paths/02-client/05_add_address.spec.js index d7fa9a8d7c..bade51718e 100644 --- a/e2e/paths/02-client/05_add_address.spec.js +++ b/e2e/paths/02-client/05_add_address.spec.js @@ -95,14 +95,14 @@ describe('Client Add address path', () => { }); it(`should confirm the default address is the expected one`, async() => { - await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'Somewhere in Thailand'); + await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'somewhere in new york'); const result = await page.waitToGetProperty(selectors.clientAddresses.defaultAddress, 'innerText'); - expect(result).toContain('Somewhere in Thailand'); + expect(result).toContain('Somewhere in New York'); }); it(`should click on the edit icon of the default address`, async() => { - await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'Somewhere in Thailand'); + await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'somewhere in new york'); await page.waitToClick(selectors.clientAddresses.firstEditAddress); await page.waitForState('client.card.address.edit'); }); diff --git a/e2e/paths/07-order/01_summary.spec.js b/e2e/paths/07-order/01_summary.spec.js index d170f290e7..f21015c52c 100644 --- a/e2e/paths/07-order/01_summary.spec.js +++ b/e2e/paths/07-order/01_summary.spec.js @@ -29,13 +29,13 @@ describe('Order summary path', () => { it('should check the summary contains the order alias', async() => { const result = await page.waitToGetProperty(selectors.orderSummary.alias, 'innerText'); - expect(result).toEqual('address 26'); + expect(result).toEqual('Many places'); }); it('should check the summary contains the order consignee', async() => { const result = await page.waitToGetProperty(selectors.orderSummary.consignee, 'innerText'); - expect(result).toEqual('Many places - Silla (Province one)'); + expect(result).toEqual('address 26 - Silla (Province one)'); }); it('should check the summary contains the order subtotal', async() => { From b3ab1fe0593416d745bfb6a5d4a7fe2ca809cfa0 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 2 Nov 2020 19:58:07 +0100 Subject: [PATCH 3/3] Sync fixes & improvements --- .../00-role_syncPrivileges.sql | 503 ++++++++++++++++++ db/changes/10240-allSaints/00-sambaConfig.sql | 10 + .../account/back/methods/role-inherit/sync.js | 113 +--- .../back/methods/user-account/sync-all.js | 11 +- .../account/back/methods/user-account/sync.js | 8 +- modules/account/back/models/ldap-config.json | 7 +- modules/account/back/models/samba-config.json | 11 +- modules/account/back/util/sync-connector.js | 5 + modules/account/back/util/sync-engine.js | 38 +- modules/account/back/util/sync-ldap.js | 143 ++++- modules/account/back/util/sync-samba.js | 102 ++-- modules/account/front/ldap/index.html | 22 +- modules/account/front/ldap/index.js | 4 +- modules/account/front/ldap/locale/es.yml | 10 +- modules/account/front/samba/index.html | 33 +- modules/account/front/samba/locale/es.yml | 9 +- 16 files changed, 801 insertions(+), 228 deletions(-) create mode 100644 db/changes/10240-allSaints/00-role_syncPrivileges.sql create mode 100644 db/changes/10240-allSaints/00-sambaConfig.sql diff --git a/db/changes/10240-allSaints/00-role_syncPrivileges.sql b/db/changes/10240-allSaints/00-role_syncPrivileges.sql new file mode 100644 index 0000000000..0e3b0d55b5 --- /dev/null +++ b/db/changes/10240-allSaints/00-role_syncPrivileges.sql @@ -0,0 +1,503 @@ +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, '%'); + + IF vIsMysql THEN + DELETE FROM mysql.user + WHERE `User` LIKE vPrefixedLike; + ELSE + DELETE FROM mysql.global_priv + WHERE `User` LIKE vPrefixedLike; + END IF; + + 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`, '{}'), + JSON_OBJECT( + 'mysql_old_password', JSON_VALUE(t.`Priv`, '$.mysql_old_password'), + 'mysql_native_password', JSON_VALUE(t.`Priv`, '$.mysql_native_password'), + 'authentication_string', JSON_VALUE(t.`Priv`, '$.authentication_string') + ) + ) + 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/10240-allSaints/00-sambaConfig.sql b/db/changes/10240-allSaints/00-sambaConfig.sql new file mode 100644 index 0000000000..e92d62cff7 --- /dev/null +++ b/db/changes/10240-allSaints/00-sambaConfig.sql @@ -0,0 +1,10 @@ +ALTER TABLE account.sambaConfig ADD adUser VARCHAR(255) DEFAULT NULL NULL COMMENT 'Active directory user'; +ALTER TABLE account.sambaConfig ADD adPassword varchar(255) DEFAULT NULL NULL COMMENT 'Active directory password'; +ALTER TABLE account.sambaConfig ADD userDn varchar(255) DEFAULT NULL NULL COMMENT 'The base DN for users'; +ALTER TABLE account.sambaConfig DROP COLUMN uidBase; +ALTER TABLE account.sambaConfig CHANGE sshPass sshPassword varchar(255) DEFAULT NULL NULL COMMENT 'The SSH password'; + +ALTER TABLE account.ldapConfig DROP COLUMN `filter`; +ALTER TABLE account.ldapConfig CHANGE baseDn userDn varchar(255) DEFAULT NULL NULL COMMENT 'The base DN to do the query'; +ALTER TABLE account.ldapConfig CHANGE host server varchar(255) NOT NULL COMMENT 'The hostname of LDAP server'; +ALTER TABLE account.ldapConfig MODIFY COLUMN password varchar(255) NOT NULL COMMENT 'The LDAP password'; diff --git a/modules/account/back/methods/role-inherit/sync.js b/modules/account/back/methods/role-inherit/sync.js index 44a9a7932f..b84e4c2fe5 100644 --- a/modules/account/back/methods/role-inherit/sync.js +++ b/modules/account/back/methods/role-inherit/sync.js @@ -1,4 +1,5 @@ -const ldap = require('../../util/ldapjs-extra'); + +const SyncEngine = require('../../util/sync-engine'); module.exports = Self => { Self.remoteMethod('sync', { @@ -10,119 +11,17 @@ module.exports = Self => { }); Self.sync = async function() { - let $ = Self.app.models; - - let ldapConfig = await $.LdapConfig.findOne({ - fields: ['host', 'rdn', 'password', 'groupDn'] - }); - let accountConfig = await $.AccountConfig.findOne({ - fields: ['idBase'] - }); - - if (!ldapConfig) return; - - // Connect - - let client = ldap.createClient({ - url: `ldap://${ldapConfig.host}:389` - }); - - let ldapPassword = Buffer - .from(ldapConfig.password, 'base64') - .toString('ascii'); - await client.bind(ldapConfig.rdn, ldapPassword); + let engine = new SyncEngine(); + await engine.init(Self.app.models); let err; try { - // Delete roles - - let opts = { - scope: 'sub', - attributes: ['dn'], - filter: 'objectClass=posixGroup' - }; - let res = await client.search(ldapConfig.groupDn, opts); - - let reqs = []; - await new Promise((resolve, reject) => { - res.on('error', err => { - if (err.name === 'NoSuchObjectError') - err = new Error(`Object '${ldapConfig.groupDn}' does not exist`); - reject(err); - }); - res.on('searchEntry', e => { - reqs.push(client.del(e.object.dn)); - }); - res.on('end', resolve); - }); - await Promise.all(reqs); - - // Recreate roles - - let roles = await $.Role.find({ - fields: ['id', 'name'] - }); - let roleRoles = await $.RoleRole.find({ - fields: ['role', 'inheritsFrom'] - }); - let roleMap = toMap(roleRoles, e => { - return {key: e.inheritsFrom, val: e.role}; - }); - - let accounts = await $.UserAccount.find({ - fields: ['id'], - include: { - relation: 'user', - scope: { - fields: ['name', 'roleFk'], - where: {active: true} - } - } - }); - let accountMap = toMap(accounts, e => { - let user = e.user(); - if (!user) return; - return {key: user.roleFk, val: user.name}; - }); - - reqs = []; - for (let role of roles) { - let newEntry = { - objectClass: ['top', 'posixGroup'], - cn: role.name, - gidNumber: accountConfig.idBase + role.id - }; - - let memberUid = []; - for (subrole of roleMap.get(role.id) || []) - memberUid = memberUid.concat(accountMap.get(subrole) || []); - - if (memberUid.length) { - memberUid.sort((a, b) => a.localeCompare(b)); - newEntry.memberUid = memberUid; - } - - let dn = `cn=${role.name},${ldapConfig.groupDn}`; - reqs.push(client.add(dn, newEntry)); - } - await Promise.all(reqs); + await engine.syncRoles(); } catch (e) { err = e; } - await client.unbind(); + await engine.deinit(); if (err) throw err; }; }; - -function toMap(array, fn) { - let map = new Map(); - for (let item of array) { - let keyVal = fn(item); - if (!keyVal) continue; - let key = keyVal.key; - if (!map.has(key)) map.set(key, []); - map.get(key).push(keyVal.val); - } - return map; -} diff --git a/modules/account/back/methods/user-account/sync-all.js b/modules/account/back/methods/user-account/sync-all.js index 3c9d0c778e..737099ce8e 100644 --- a/modules/account/back/methods/user-account/sync-all.js +++ b/modules/account/back/methods/user-account/sync-all.js @@ -13,25 +13,24 @@ module.exports = Self => { Self.syncAll = async function() { let $ = Self.app.models; - let se = new SyncEngine(); - await se.init($); + let engine = new SyncEngine(); + await engine.init($); - let usersToSync = await se.getUsers(); + let usersToSync = await engine.getUsers(); usersToSync = Array.from(usersToSync.values()) .sort((a, b) => a.localeCompare(b)); for (let user of usersToSync) { try { console.log(`Synchronizing user '${user}'`); - await se.sync(user); + await engine.sync(user); console.log(` -> '${user}' sinchronized`); } catch (err) { console.error(` -> '${user}' synchronization error:`, err.message); } } - await se.deinit(); - + await engine.deinit(); await $.RoleInherit.sync(); }; }; diff --git a/modules/account/back/methods/user-account/sync.js b/modules/account/back/methods/user-account/sync.js index eff316c928..f16b2c0527 100644 --- a/modules/account/back/methods/user-account/sync.js +++ b/modules/account/back/methods/user-account/sync.js @@ -34,17 +34,17 @@ module.exports = Self => { if (user && isSync) return; let err; - let se = new SyncEngine(); - await se.init($); + let engine = new SyncEngine(); + await engine.init($); try { - await se.sync(userName, password, true); + await engine.sync(userName, password, true); await $.UserSync.destroyById(userName); } catch (e) { err = e; } - await se.deinit(); + await engine.deinit(); if (err) throw err; }; }; diff --git a/modules/account/back/models/ldap-config.json b/modules/account/back/models/ldap-config.json index e3061d651f..f7d3ab08b9 100644 --- a/modules/account/back/models/ldap-config.json +++ b/modules/account/back/models/ldap-config.json @@ -11,7 +11,7 @@ "type": "number", "id": true }, - "host": { + "server": { "type": "string", "required": true }, @@ -23,10 +23,7 @@ "type": "string", "required": true }, - "baseDn": { - "type": "string" - }, - "filter": { + "userDn": { "type": "string" }, "groupDn": { diff --git a/modules/account/back/models/samba-config.json b/modules/account/back/models/samba-config.json index ffbcce4eb3..d729ca1114 100644 --- a/modules/account/back/models/samba-config.json +++ b/modules/account/back/models/samba-config.json @@ -18,7 +18,16 @@ "sshUser": { "type": "string" }, - "sshPass": { + "sshPassword": { + "type": "string" + }, + "adUser": { + "type": "string" + }, + "adPassword": { + "type": "string" + }, + "userDn": { "type": "string" } } diff --git a/modules/account/back/util/sync-connector.js b/modules/account/back/util/sync-connector.js index 4b8c9ed6af..db7d230be6 100644 --- a/modules/account/back/util/sync-connector.js +++ b/modules/account/back/util/sync-connector.js @@ -37,6 +37,11 @@ class SyncConnector { */ async syncGroups(user, userName) {} + /** + * Synchronizes roles. + */ + async syncRoles() {} + /** * Deinitalizes the connector. */ diff --git a/modules/account/back/util/sync-engine.js b/modules/account/back/util/sync-engine.js index 5ca9cac657..6fd256c986 100644 --- a/modules/account/back/util/sync-engine.js +++ b/modules/account/back/util/sync-engine.js @@ -19,8 +19,10 @@ module.exports = class SyncEngine { for (let ConnectorClass of SyncConnector.connectors) { let connector = new ConnectorClass(); Object.assign(connector, { - se: this, - $ + engine: this, + $, + accountConfig, + mailConfig }); if (!await connector.init()) continue; connectors.push(connector); @@ -80,27 +82,20 @@ module.exports = class SyncEngine { } }); - let extraParams; - let hasAccount = false; - - if (user) { - hasAccount = user.active - && await $.UserAccount.exists(user.id); - - extraParams = { - corporateMail: `${userName}@${mailConfig.domain}`, - uidNumber: accountConfig.idBase + user.id - }; - } - let info = { user, - extraParams, - hasAccount, - accountConfig, - mailConfig + hasAccount: false }; + if (user) { + let exists = await $.UserAccount.exists(user.id); + Object.assign(info, { + hasAccount: user.active && exists, + corporateMail: `${userName}@${mailConfig.domain}`, + uidNumber: accountConfig.idBase + user.id + }); + } + let errs = []; for (let connector of this.connectors) { @@ -116,6 +111,11 @@ module.exports = class SyncEngine { if (errs.length) throw errs[0]; } + async syncRoles() { + for (let connector of this.connectors) + await connector.syncRoles(); + } + async getUsers() { let usersToSync = new Set(); diff --git a/modules/account/back/util/sync-ldap.js b/modules/account/back/util/sync-ldap.js index d72520ed6f..3f98633d98 100644 --- a/modules/account/back/util/sync-ldap.js +++ b/modules/account/back/util/sync-ldap.js @@ -7,18 +7,20 @@ const crypto = require('crypto'); class SyncLdap extends SyncConnector { async init() { let ldapConfig = await this.$.LdapConfig.findOne({ - fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn'] + fields: [ + 'server', + 'rdn', + 'password', + 'userDn', + 'groupDn' + ] }); if (!ldapConfig) return false; let client = ldap.createClient({ - url: `ldap://${ldapConfig.host}:389` + url: ldapConfig.server }); - - let ldapPassword = Buffer - .from(ldapConfig.password, 'base64') - .toString('ascii'); - await client.bind(ldapConfig.rdn, ldapPassword); + await client.bind(ldapConfig.rdn, ldapConfig.password); Object.assign(this, { ldapConfig, @@ -36,16 +38,12 @@ class SyncLdap extends SyncConnector { let { ldapConfig, client, + accountConfig } = this; - let { - user, - hasAccount, - extraParams, - accountConfig - } = info; + let {user} = info; - let res = await client.search(ldapConfig.baseDn, { + let res = await client.search(ldapConfig.userDn, { scope: 'sub', attributes: ['userPassword', 'sambaNTPassword'], filter: `&(uid=${userName})` @@ -59,13 +57,13 @@ class SyncLdap extends SyncConnector { }); try { - let dn = `uid=${userName},${ldapConfig.baseDn}`; + let dn = `uid=${userName},${ldapConfig.userDn}`; await client.del(dn); } catch (e) { if (e.name !== 'NoSuchObjectError') throw e; } - if (!hasAccount) { + if (!info.hasAccount) { if (oldUser) console.log(` -> '${userName}' removed from LDAP`); return; @@ -77,7 +75,7 @@ class SyncLdap extends SyncConnector { ? nameArgs.splice(1).join(' ') : '-'; - let dn = `uid=${userName},${ldapConfig.baseDn}`; + let dn = `uid=${userName},${ldapConfig.userDn}`; let newEntry = { uid: userName, objectClass: [ @@ -89,11 +87,11 @@ class SyncLdap extends SyncConnector { displayName: nickname, givenName: nameArgs[0], sn, - mail: extraParams.corporateMail, + mail: info.corporateMail, preferredLanguage: user.lang, homeDirectory: `${accountConfig.homedir}/${userName}`, loginShell: accountConfig.shell, - uidNumber: extraParams.uidNumber, + uidNumber: info.uidNumber, gidNumber: accountConfig.idBase + user.roleFk, sambaSID: '-' }; @@ -137,11 +135,6 @@ class SyncLdap extends SyncConnector { client } = this; - let { - user, - hasAccount - } = info; - let res = await client.search(ldapConfig.groupDn, { scope: 'sub', attributes: ['dn'], @@ -165,10 +158,10 @@ class SyncLdap extends SyncConnector { } await Promise.all(reqs); - if (!hasAccount) return; + if (!info.hasAccount) return; reqs = []; - for (let role of user.roles()) { + for (let role of info.user.roles()) { let change = new ldap.Change({ operation: 'add', modification: {memberUid: userName} @@ -186,7 +179,7 @@ class SyncLdap extends SyncConnector { client } = this; - let res = await client.search(ldapConfig.baseDn, { + let res = await client.search(ldapConfig.userDn, { scope: 'sub', attributes: ['uid'], filter: `uid=*` @@ -198,7 +191,103 @@ class SyncLdap extends SyncConnector { res.on('end', resolve); }); } + + async syncRoles() { + let { + $, + ldapConfig, + client, + accountConfig + } = this; + + // Delete roles + + let opts = { + scope: 'sub', + attributes: ['dn'], + filter: 'objectClass=posixGroup' + }; + let res = await client.search(ldapConfig.groupDn, opts); + + let reqs = []; + await new Promise((resolve, reject) => { + res.on('error', err => { + if (err.name === 'NoSuchObjectError') + err = new Error(`Object '${ldapConfig.groupDn}' does not exist`); + reject(err); + }); + res.on('searchEntry', e => { + reqs.push(client.del(e.object.dn)); + }); + res.on('end', resolve); + }); + await Promise.all(reqs); + + // Recreate roles + + let roles = await $.Role.find({ + fields: ['id', 'name'] + }); + let roleRoles = await $.RoleRole.find({ + fields: ['role', 'inheritsFrom'] + }); + let roleMap = toMap(roleRoles, e => { + return {key: e.inheritsFrom, val: e.role}; + }); + + let accounts = await $.UserAccount.find({ + fields: ['id'], + include: { + relation: 'user', + scope: { + fields: ['name', 'roleFk'], + where: {active: true} + } + } + }); + let accountMap = toMap(accounts, e => { + let user = e.user(); + if (!user) return; + return {key: user.roleFk, val: user.name}; + }); + + console.log; + + reqs = []; + for (let role of roles) { + let newEntry = { + objectClass: ['top', 'posixGroup'], + cn: role.name, + gidNumber: accountConfig.idBase + role.id + }; + + let memberUid = []; + for (let subrole of roleMap.get(role.id) || []) + memberUid = memberUid.concat(accountMap.get(subrole) || []); + + if (memberUid.length) { + memberUid.sort((a, b) => a.localeCompare(b)); + newEntry.memberUid = memberUid; + } + + let dn = `cn=${role.name},${ldapConfig.groupDn}`; + reqs.push(client.add(dn, newEntry)); + } + await Promise.all(reqs); + } } SyncConnector.connectors.push(SyncLdap); module.exports = SyncLdap; + +function toMap(array, fn) { + let map = new Map(); + for (let item of array) { + let keyVal = fn(item); + if (!keyVal) continue; + let key = keyVal.key; + if (!map.has(key)) map.set(key, []); + map.get(key).push(keyVal.val); + } + return map; +} diff --git a/modules/account/back/util/sync-samba.js b/modules/account/back/util/sync-samba.js index c6ef556e58..6e5ef9d5a0 100644 --- a/modules/account/back/util/sync-samba.js +++ b/modules/account/back/util/sync-samba.js @@ -1,60 +1,71 @@ const SyncConnector = require('./sync-connector'); const ssh = require('node-ssh'); +const ldap = require('./ldapjs-extra'); class SyncSamba extends SyncConnector { async init() { let sambaConfig = await this.$.SambaConfig.findOne({ - fields: ['host', 'sshUser', 'sshPass'] + fields: [ + 'host', + 'sshUser', + 'sshPassword', + 'adUser', + 'adPassword', + 'userDn' + ] }); if (!sambaConfig) return false; - let sshPassword = Buffer - .from(sambaConfig.sshPass, 'base64') - .toString('ascii'); - let client = new ssh.NodeSSH(); await client.connect({ host: sambaConfig.host, username: sambaConfig.sshUser, - password: sshPassword + password: sambaConfig.sshPassword + }); + + let adClient = ldap.createClient({ + url: `ldaps://${sambaConfig.host}:636`, + tlsOptions: {rejectUnauthorized: false} }); Object.assign(this, { sambaConfig, - client + client, + adClient }); return true; } async deinit() { - if (this.client) - await this.client.dispose(); + if (!this.client) return; + await this.client.dispose(); + await this.adClient.unbind(); } async sync(info, userName, password) { - let { - client - } = this; + let {client} = this; - let { - hasAccount, - extraParams - } = info; - - if (hasAccount) { + if (info.hasAccount) { try { await client.exec('samba-tool user create', [ userName, - '--uid-number', `${extraParams.uidNumber}`, - '--mail-address', extraParams.corporateMail, + '--uid-number', `${info.uidNumber}`, + '--mail-address', info.corporateMail, '--random-password' ]); + await client.exec('samba-tool user setexpiry', [ + userName, + '--noexpiry' + ]); + await client.exec('mkhomedir_helper', [ + userName, + '0027' + ]); } catch (e) {} - await client.exec('samba-tool user setexpiry', [ - userName, - '--noexpiry' + await client.exec('samba-tool user enable', [ + userName ]); if (password) { @@ -62,15 +73,7 @@ class SyncSamba extends SyncConnector { userName, '--newpassword', password ]); - await client.exec('samba-tool user enable', [ - userName - ]); } - - await client.exec('mkhomedir_helper', [ - userName, - '0027' - ]); } else { try { await client.exec('samba-tool user disable', [ @@ -81,11 +84,40 @@ class SyncSamba extends SyncConnector { } } + /** + * Gets enabled users from Samba. + * + * Summary of userAccountControl flags: + * https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties + * + * @param {Set} usersToSync + */ async getUsers(usersToSync) { - let {client} = this; - let res = await client.execCommand('samba-tool user list'); - let users = res.stdout.split('\n'); - for (let user of users) usersToSync.add(user.trim()); + let { + sambaConfig, + adClient + } = this; + + await adClient.bind(sambaConfig.adUser, sambaConfig.adPassword); + + let opts = { + scope: 'sub', + attributes: ['sAMAccountName'], + filter: '(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))' + }; + let res = await adClient.search(sambaConfig.userDn, opts); + + await new Promise((resolve, reject) => { + res.on('error', err => { + if (err.name === 'NoSuchObjectError') + err = new Error(`Object '${sambaConfig.userDn}' does not exist`); + reject(err); + }); + res.on('searchEntry', e => { + usersToSync.add(e.object.sAMAccountName); + }); + res.on('end', resolve); + }); } } diff --git a/modules/account/front/ldap/index.html b/modules/account/front/ldap/index.html index 5c9ec7625b..2ba1ceda69 100644 --- a/modules/account/front/ldap/index.html +++ b/modules/account/front/ldap/index.html @@ -11,9 +11,17 @@ class="vn-w-md"> + + + + @@ -25,18 +33,12 @@ - - this.vnApp.showSuccess(this.$t('LDAP users synchronized'))); + .then(() => this.vnApp.showSuccess(this.$t('Users synchronized!'))); } onUserSync() { @@ -15,7 +15,7 @@ export default class Controller extends Section { let params = {password: this.syncPassword}; return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params) - .then(() => this.vnApp.showSuccess(this.$t('User synchronized'))); + .then(() => this.vnApp.showSuccess(this.$t('User synchronized!'))); } onSyncClose() { diff --git a/modules/account/front/ldap/locale/es.yml b/modules/account/front/ldap/locale/es.yml index e7481699f4..56fe623e8b 100644 --- a/modules/account/front/ldap/locale/es.yml +++ b/modules/account/front/ldap/locale/es.yml @@ -1,7 +1,7 @@ -Host: Host +Enable synchronization: Habilitar sincronización +Server: Servidor RDN: RDN -Base DN: DN base -Password should be base64 encoded: La contraseña debe estar codificada en base64 +User DN: DN usuarios Filter: Filtro Group DN: DN grupos Synchronize now: Sincronizar ahora @@ -9,8 +9,8 @@ Synchronize user: Sincronizar usuario If password is not specified, just user attributes are synchronized: >- Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario Synchronizing in the background: Sincronizando en segundo plano -LDAP users synchronized: Usuarios LDAP sincronizados +Users synchronized!: ¡Usuarios sincronizados! Username: Nombre de usuario Synchronize: Sincronizar Please enter the username: Por favor introduce el nombre de usuario -User synchronized: Usuario sincronizado +User synchronized!: ¡Usuario sincronizado! diff --git a/modules/account/front/samba/index.html b/modules/account/front/samba/index.html index 57461cb14c..9b66cdc2a9 100644 --- a/modules/account/front/samba/index.html +++ b/modules/account/front/samba/index.html @@ -11,24 +11,47 @@ class="vn-w-md"> + + + + + + + + + + diff --git a/modules/account/front/samba/locale/es.yml b/modules/account/front/samba/locale/es.yml index 7cfc4c7441..a036bb3cc8 100644 --- a/modules/account/front/samba/locale/es.yml +++ b/modules/account/front/samba/locale/es.yml @@ -1,2 +1,7 @@ -SSH host: Host SSH -Password should be base64 encoded: La contraseña debe estar codificada en base64 +Enable synchronization: Habilitar sincronización +Host: Host +SSH user: Usuario SSH +SSH password: Contraseña SSH +AD user: Usuario AD +AD password: Contraseña AD +User DN: DN usuarios