Sync fixes & improvements
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
parent
414c0931eb
commit
b3ab1fe059
|
@ -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 ;
|
|
@ -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';
|
|
@ -1,4 +1,5 @@
|
||||||
const ldap = require('../../util/ldapjs-extra');
|
|
||||||
|
const SyncEngine = require('../../util/sync-engine');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('sync', {
|
Self.remoteMethod('sync', {
|
||||||
|
@ -10,119 +11,17 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.sync = async function() {
|
Self.sync = async function() {
|
||||||
let $ = Self.app.models;
|
let engine = new SyncEngine();
|
||||||
|
await engine.init(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 err;
|
let err;
|
||||||
try {
|
try {
|
||||||
// Delete roles
|
await engine.syncRoles();
|
||||||
|
|
||||||
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);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.unbind();
|
await engine.deinit();
|
||||||
if (err) throw err;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,25 +13,24 @@ module.exports = Self => {
|
||||||
Self.syncAll = async function() {
|
Self.syncAll = async function() {
|
||||||
let $ = Self.app.models;
|
let $ = Self.app.models;
|
||||||
|
|
||||||
let se = new SyncEngine();
|
let engine = new SyncEngine();
|
||||||
await se.init($);
|
await engine.init($);
|
||||||
|
|
||||||
let usersToSync = await se.getUsers();
|
let usersToSync = await engine.getUsers();
|
||||||
usersToSync = Array.from(usersToSync.values())
|
usersToSync = Array.from(usersToSync.values())
|
||||||
.sort((a, b) => a.localeCompare(b));
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
for (let user of usersToSync) {
|
for (let user of usersToSync) {
|
||||||
try {
|
try {
|
||||||
console.log(`Synchronizing user '${user}'`);
|
console.log(`Synchronizing user '${user}'`);
|
||||||
await se.sync(user);
|
await engine.sync(user);
|
||||||
console.log(` -> '${user}' sinchronized`);
|
console.log(` -> '${user}' sinchronized`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(` -> '${user}' synchronization error:`, err.message);
|
console.error(` -> '${user}' synchronization error:`, err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await se.deinit();
|
await engine.deinit();
|
||||||
|
|
||||||
await $.RoleInherit.sync();
|
await $.RoleInherit.sync();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,17 +34,17 @@ module.exports = Self => {
|
||||||
if (user && isSync) return;
|
if (user && isSync) return;
|
||||||
|
|
||||||
let err;
|
let err;
|
||||||
let se = new SyncEngine();
|
let engine = new SyncEngine();
|
||||||
await se.init($);
|
await engine.init($);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await se.sync(userName, password, true);
|
await engine.sync(userName, password, true);
|
||||||
await $.UserSync.destroyById(userName);
|
await $.UserSync.destroyById(userName);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
await se.deinit();
|
await engine.deinit();
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"id": true
|
"id": true
|
||||||
},
|
},
|
||||||
"host": {
|
"server": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
@ -23,10 +23,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"baseDn": {
|
"userDn": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"filter": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"groupDn": {
|
"groupDn": {
|
||||||
|
|
|
@ -18,7 +18,16 @@
|
||||||
"sshUser": {
|
"sshUser": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"sshPass": {
|
"sshPassword": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"adUser": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"adPassword": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"userDn": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,11 @@ class SyncConnector {
|
||||||
*/
|
*/
|
||||||
async syncGroups(user, userName) {}
|
async syncGroups(user, userName) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes roles.
|
||||||
|
*/
|
||||||
|
async syncRoles() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deinitalizes the connector.
|
* Deinitalizes the connector.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,8 +19,10 @@ module.exports = class SyncEngine {
|
||||||
for (let ConnectorClass of SyncConnector.connectors) {
|
for (let ConnectorClass of SyncConnector.connectors) {
|
||||||
let connector = new ConnectorClass();
|
let connector = new ConnectorClass();
|
||||||
Object.assign(connector, {
|
Object.assign(connector, {
|
||||||
se: this,
|
engine: this,
|
||||||
$
|
$,
|
||||||
|
accountConfig,
|
||||||
|
mailConfig
|
||||||
});
|
});
|
||||||
if (!await connector.init()) continue;
|
if (!await connector.init()) continue;
|
||||||
connectors.push(connector);
|
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 = {
|
let info = {
|
||||||
user,
|
user,
|
||||||
extraParams,
|
hasAccount: false
|
||||||
hasAccount,
|
|
||||||
accountConfig,
|
|
||||||
mailConfig
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = [];
|
let errs = [];
|
||||||
|
|
||||||
for (let connector of this.connectors) {
|
for (let connector of this.connectors) {
|
||||||
|
@ -116,6 +111,11 @@ module.exports = class SyncEngine {
|
||||||
if (errs.length) throw errs[0];
|
if (errs.length) throw errs[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncRoles() {
|
||||||
|
for (let connector of this.connectors)
|
||||||
|
await connector.syncRoles();
|
||||||
|
}
|
||||||
|
|
||||||
async getUsers() {
|
async getUsers() {
|
||||||
let usersToSync = new Set();
|
let usersToSync = new Set();
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,20 @@ const crypto = require('crypto');
|
||||||
class SyncLdap extends SyncConnector {
|
class SyncLdap extends SyncConnector {
|
||||||
async init() {
|
async init() {
|
||||||
let ldapConfig = await this.$.LdapConfig.findOne({
|
let ldapConfig = await this.$.LdapConfig.findOne({
|
||||||
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
fields: [
|
||||||
|
'server',
|
||||||
|
'rdn',
|
||||||
|
'password',
|
||||||
|
'userDn',
|
||||||
|
'groupDn'
|
||||||
|
]
|
||||||
});
|
});
|
||||||
if (!ldapConfig) return false;
|
if (!ldapConfig) return false;
|
||||||
|
|
||||||
let client = ldap.createClient({
|
let client = ldap.createClient({
|
||||||
url: `ldap://${ldapConfig.host}:389`
|
url: ldapConfig.server
|
||||||
});
|
});
|
||||||
|
await client.bind(ldapConfig.rdn, ldapConfig.password);
|
||||||
let ldapPassword = Buffer
|
|
||||||
.from(ldapConfig.password, 'base64')
|
|
||||||
.toString('ascii');
|
|
||||||
await client.bind(ldapConfig.rdn, ldapPassword);
|
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
ldapConfig,
|
ldapConfig,
|
||||||
|
@ -36,16 +38,12 @@ class SyncLdap extends SyncConnector {
|
||||||
let {
|
let {
|
||||||
ldapConfig,
|
ldapConfig,
|
||||||
client,
|
client,
|
||||||
|
accountConfig
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
let {
|
let {user} = info;
|
||||||
user,
|
|
||||||
hasAccount,
|
|
||||||
extraParams,
|
|
||||||
accountConfig
|
|
||||||
} = info;
|
|
||||||
|
|
||||||
let res = await client.search(ldapConfig.baseDn, {
|
let res = await client.search(ldapConfig.userDn, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['userPassword', 'sambaNTPassword'],
|
attributes: ['userPassword', 'sambaNTPassword'],
|
||||||
filter: `&(uid=${userName})`
|
filter: `&(uid=${userName})`
|
||||||
|
@ -59,13 +57,13 @@ class SyncLdap extends SyncConnector {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
let dn = `uid=${userName},${ldapConfig.userDn}`;
|
||||||
await client.del(dn);
|
await client.del(dn);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name !== 'NoSuchObjectError') throw e;
|
if (e.name !== 'NoSuchObjectError') throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasAccount) {
|
if (!info.hasAccount) {
|
||||||
if (oldUser)
|
if (oldUser)
|
||||||
console.log(` -> '${userName}' removed from LDAP`);
|
console.log(` -> '${userName}' removed from LDAP`);
|
||||||
return;
|
return;
|
||||||
|
@ -77,7 +75,7 @@ class SyncLdap extends SyncConnector {
|
||||||
? nameArgs.splice(1).join(' ')
|
? nameArgs.splice(1).join(' ')
|
||||||
: '-';
|
: '-';
|
||||||
|
|
||||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
let dn = `uid=${userName},${ldapConfig.userDn}`;
|
||||||
let newEntry = {
|
let newEntry = {
|
||||||
uid: userName,
|
uid: userName,
|
||||||
objectClass: [
|
objectClass: [
|
||||||
|
@ -89,11 +87,11 @@ class SyncLdap extends SyncConnector {
|
||||||
displayName: nickname,
|
displayName: nickname,
|
||||||
givenName: nameArgs[0],
|
givenName: nameArgs[0],
|
||||||
sn,
|
sn,
|
||||||
mail: extraParams.corporateMail,
|
mail: info.corporateMail,
|
||||||
preferredLanguage: user.lang,
|
preferredLanguage: user.lang,
|
||||||
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
||||||
loginShell: accountConfig.shell,
|
loginShell: accountConfig.shell,
|
||||||
uidNumber: extraParams.uidNumber,
|
uidNumber: info.uidNumber,
|
||||||
gidNumber: accountConfig.idBase + user.roleFk,
|
gidNumber: accountConfig.idBase + user.roleFk,
|
||||||
sambaSID: '-'
|
sambaSID: '-'
|
||||||
};
|
};
|
||||||
|
@ -137,11 +135,6 @@ class SyncLdap extends SyncConnector {
|
||||||
client
|
client
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
let {
|
|
||||||
user,
|
|
||||||
hasAccount
|
|
||||||
} = info;
|
|
||||||
|
|
||||||
let res = await client.search(ldapConfig.groupDn, {
|
let res = await client.search(ldapConfig.groupDn, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['dn'],
|
attributes: ['dn'],
|
||||||
|
@ -165,10 +158,10 @@ class SyncLdap extends SyncConnector {
|
||||||
}
|
}
|
||||||
await Promise.all(reqs);
|
await Promise.all(reqs);
|
||||||
|
|
||||||
if (!hasAccount) return;
|
if (!info.hasAccount) return;
|
||||||
|
|
||||||
reqs = [];
|
reqs = [];
|
||||||
for (let role of user.roles()) {
|
for (let role of info.user.roles()) {
|
||||||
let change = new ldap.Change({
|
let change = new ldap.Change({
|
||||||
operation: 'add',
|
operation: 'add',
|
||||||
modification: {memberUid: userName}
|
modification: {memberUid: userName}
|
||||||
|
@ -186,7 +179,7 @@ class SyncLdap extends SyncConnector {
|
||||||
client
|
client
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
let res = await client.search(ldapConfig.baseDn, {
|
let res = await client.search(ldapConfig.userDn, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['uid'],
|
attributes: ['uid'],
|
||||||
filter: `uid=*`
|
filter: `uid=*`
|
||||||
|
@ -198,7 +191,103 @@ class SyncLdap extends SyncConnector {
|
||||||
res.on('end', resolve);
|
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);
|
SyncConnector.connectors.push(SyncLdap);
|
||||||
module.exports = 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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,60 +1,71 @@
|
||||||
|
|
||||||
const SyncConnector = require('./sync-connector');
|
const SyncConnector = require('./sync-connector');
|
||||||
const ssh = require('node-ssh');
|
const ssh = require('node-ssh');
|
||||||
|
const ldap = require('./ldapjs-extra');
|
||||||
|
|
||||||
class SyncSamba extends SyncConnector {
|
class SyncSamba extends SyncConnector {
|
||||||
async init() {
|
async init() {
|
||||||
let sambaConfig = await this.$.SambaConfig.findOne({
|
let sambaConfig = await this.$.SambaConfig.findOne({
|
||||||
fields: ['host', 'sshUser', 'sshPass']
|
fields: [
|
||||||
|
'host',
|
||||||
|
'sshUser',
|
||||||
|
'sshPassword',
|
||||||
|
'adUser',
|
||||||
|
'adPassword',
|
||||||
|
'userDn'
|
||||||
|
]
|
||||||
});
|
});
|
||||||
if (!sambaConfig) return false;
|
if (!sambaConfig) return false;
|
||||||
|
|
||||||
let sshPassword = Buffer
|
|
||||||
.from(sambaConfig.sshPass, 'base64')
|
|
||||||
.toString('ascii');
|
|
||||||
|
|
||||||
let client = new ssh.NodeSSH();
|
let client = new ssh.NodeSSH();
|
||||||
await client.connect({
|
await client.connect({
|
||||||
host: sambaConfig.host,
|
host: sambaConfig.host,
|
||||||
username: sambaConfig.sshUser,
|
username: sambaConfig.sshUser,
|
||||||
password: sshPassword
|
password: sambaConfig.sshPassword
|
||||||
|
});
|
||||||
|
|
||||||
|
let adClient = ldap.createClient({
|
||||||
|
url: `ldaps://${sambaConfig.host}:636`,
|
||||||
|
tlsOptions: {rejectUnauthorized: false}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
sambaConfig,
|
sambaConfig,
|
||||||
client
|
client,
|
||||||
|
adClient
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deinit() {
|
async deinit() {
|
||||||
if (this.client)
|
if (!this.client) return;
|
||||||
await this.client.dispose();
|
await this.client.dispose();
|
||||||
|
await this.adClient.unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync(info, userName, password) {
|
async sync(info, userName, password) {
|
||||||
let {
|
let {client} = this;
|
||||||
client
|
|
||||||
} = this;
|
|
||||||
|
|
||||||
let {
|
if (info.hasAccount) {
|
||||||
hasAccount,
|
|
||||||
extraParams
|
|
||||||
} = info;
|
|
||||||
|
|
||||||
if (hasAccount) {
|
|
||||||
try {
|
try {
|
||||||
await client.exec('samba-tool user create', [
|
await client.exec('samba-tool user create', [
|
||||||
userName,
|
userName,
|
||||||
'--uid-number', `${extraParams.uidNumber}`,
|
'--uid-number', `${info.uidNumber}`,
|
||||||
'--mail-address', extraParams.corporateMail,
|
'--mail-address', info.corporateMail,
|
||||||
'--random-password'
|
'--random-password'
|
||||||
]);
|
]);
|
||||||
|
await client.exec('samba-tool user setexpiry', [
|
||||||
|
userName,
|
||||||
|
'--noexpiry'
|
||||||
|
]);
|
||||||
|
await client.exec('mkhomedir_helper', [
|
||||||
|
userName,
|
||||||
|
'0027'
|
||||||
|
]);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
await client.exec('samba-tool user setexpiry', [
|
await client.exec('samba-tool user enable', [
|
||||||
userName,
|
userName
|
||||||
'--noexpiry'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (password) {
|
if (password) {
|
||||||
|
@ -62,15 +73,7 @@ class SyncSamba extends SyncConnector {
|
||||||
userName,
|
userName,
|
||||||
'--newpassword', password
|
'--newpassword', password
|
||||||
]);
|
]);
|
||||||
await client.exec('samba-tool user enable', [
|
|
||||||
userName
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.exec('mkhomedir_helper', [
|
|
||||||
userName,
|
|
||||||
'0027'
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await client.exec('samba-tool user disable', [
|
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) {
|
async getUsers(usersToSync) {
|
||||||
let {client} = this;
|
let {
|
||||||
let res = await client.execCommand('samba-tool user list');
|
sambaConfig,
|
||||||
let users = res.stdout.split('\n');
|
adClient
|
||||||
for (let user of users) usersToSync.add(user.trim());
|
} = 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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,17 @@
|
||||||
class="vn-w-md">
|
class="vn-w-md">
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-vertical>
|
<vn-vertical>
|
||||||
|
<vn-check
|
||||||
|
label="Enable synchronization"
|
||||||
|
ng-model="watcher.hasData">
|
||||||
|
</vn-check>
|
||||||
|
</vn-vertical>
|
||||||
|
<vn-vertical
|
||||||
|
ng-if="watcher.hasData"
|
||||||
|
class="vn-mt-md">
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Host"
|
label="Server"
|
||||||
ng-model="$ctrl.config.host"
|
ng-model="$ctrl.config.server"
|
||||||
rule="LdapConfig"
|
rule="LdapConfig"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
@ -25,18 +33,12 @@
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Password"
|
label="Password"
|
||||||
ng-model="$ctrl.config.password"
|
ng-model="$ctrl.config.password"
|
||||||
info="Password should be base64 encoded"
|
|
||||||
type="password"
|
type="password"
|
||||||
rule="LdapConfig">
|
rule="LdapConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Base DN"
|
label="User DN"
|
||||||
ng-model="$ctrl.config.baseDn"
|
ng-model="$ctrl.config.userDn"
|
||||||
rule="LdapConfig">
|
|
||||||
</vn-textfield>
|
|
||||||
<vn-textfield
|
|
||||||
label="Filter"
|
|
||||||
ng-model="$ctrl.config.filter"
|
|
||||||
rule="LdapConfig">
|
rule="LdapConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default class Controller extends Section {
|
||||||
onSynchronizeAll() {
|
onSynchronizeAll() {
|
||||||
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
|
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
|
||||||
this.$http.patch(`UserAccounts/syncAll`)
|
this.$http.patch(`UserAccounts/syncAll`)
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('LDAP users synchronized')));
|
.then(() => this.vnApp.showSuccess(this.$t('Users synchronized!')));
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserSync() {
|
onUserSync() {
|
||||||
|
@ -15,7 +15,7 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
let params = {password: this.syncPassword};
|
let params = {password: this.syncPassword};
|
||||||
return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params)
|
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() {
|
onSyncClose() {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Host: Host
|
Enable synchronization: Habilitar sincronización
|
||||||
|
Server: Servidor
|
||||||
RDN: RDN
|
RDN: RDN
|
||||||
Base DN: DN base
|
User DN: DN usuarios
|
||||||
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
|
||||||
Filter: Filtro
|
Filter: Filtro
|
||||||
Group DN: DN grupos
|
Group DN: DN grupos
|
||||||
Synchronize now: Sincronizar ahora
|
Synchronize now: Sincronizar ahora
|
||||||
|
@ -9,8 +9,8 @@ Synchronize user: Sincronizar usuario
|
||||||
If password is not specified, just user attributes are synchronized: >-
|
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
|
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||||
Synchronizing in the background: Sincronizando en segundo plano
|
Synchronizing in the background: Sincronizando en segundo plano
|
||||||
LDAP users synchronized: Usuarios LDAP sincronizados
|
Users synchronized!: ¡Usuarios sincronizados!
|
||||||
Username: Nombre de usuario
|
Username: Nombre de usuario
|
||||||
Synchronize: Sincronizar
|
Synchronize: Sincronizar
|
||||||
Please enter the username: Por favor introduce el nombre de usuario
|
Please enter the username: Por favor introduce el nombre de usuario
|
||||||
User synchronized: Usuario sincronizado
|
User synchronized!: ¡Usuario sincronizado!
|
||||||
|
|
|
@ -11,24 +11,47 @@
|
||||||
class="vn-w-md">
|
class="vn-w-md">
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-vertical>
|
<vn-vertical>
|
||||||
|
<vn-check
|
||||||
|
label="Enable synchronization"
|
||||||
|
ng-model="watcher.hasData">
|
||||||
|
</vn-check>
|
||||||
|
</vn-vertical>
|
||||||
|
<vn-vertical
|
||||||
|
ng-if="watcher.hasData"
|
||||||
|
class="vn-mt-md">
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="SSH host"
|
label="Host"
|
||||||
ng-model="$ctrl.config.host"
|
ng-model="$ctrl.config.host"
|
||||||
rule="SambaConfig"
|
rule="SambaConfig"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="User"
|
label="SSH user"
|
||||||
ng-model="$ctrl.config.sshUser"
|
ng-model="$ctrl.config.sshUser"
|
||||||
rule="SambaConfig">
|
rule="SambaConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Password"
|
label="SSH password"
|
||||||
ng-model="$ctrl.config.sshPass"
|
ng-model="$ctrl.config.sshPassword"
|
||||||
info="Password should be base64 encoded"
|
|
||||||
type="password"
|
type="password"
|
||||||
rule="SambaConfig">
|
rule="SambaConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="AD user"
|
||||||
|
ng-model="$ctrl.config.adUser"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="AD password"
|
||||||
|
ng-model="$ctrl.config.adPassword"
|
||||||
|
type="password"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="User DN"
|
||||||
|
ng-model="$ctrl.config.userDn"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
</vn-vertical>
|
</vn-vertical>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-button-bar>
|
<vn-button-bar>
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
SSH host: Host SSH
|
Enable synchronization: Habilitar sincronización
|
||||||
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
Host: Host
|
||||||
|
SSH user: Usuario SSH
|
||||||
|
SSH password: Contraseña SSH
|
||||||
|
AD user: Usuario AD
|
||||||
|
AD password: Contraseña AD
|
||||||
|
User DN: DN usuarios
|
||||||
|
|
Loading…
Reference in New Issue