Skip to content

Commit f0d7103

Browse files
committed
feat: first attempt to create a user manager for PostgreSQL
Refs #80
1 parent 1da8683 commit f0d7103

6 files changed

Lines changed: 1862 additions & 4 deletions

File tree

heidisql.lpi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,13 @@ if TargetOS='win64' then
455455
<Filename Value="source\vktable.pas"/>
456456
<IsPartOfProject Value="True"/>
457457
</Unit>
458+
<Unit>
459+
<Filename Value="source\rolemanagerpg.pas"/>
460+
<IsPartOfProject Value="True"/>
461+
<ComponentName Value="RoleManagerPgForm"/>
462+
<HasResources Value="True"/>
463+
<ResourceBaseClass Value="Form"/>
464+
</Unit>
458465
</Units>
459466
</ProjectOptions>
460467
<CompilerOptions>

source/dbstructures.pas

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,29 @@ interface
5050
qShowFunctionStatus, qShowProcedureStatus, qShowTriggers, qShowEvents, qShowCreateTrigger,
5151
qHelpKeyword, qShowWarnings, qGetEnumTypes,
5252
qDropUser, qCreateRole, qDropRole, qReloadPrivileges, qGrantRole, qRevokeRole, qSetDefaultRole,
53-
qAutoInc, qIndexVisible, qIndexInvisible, qGetAuthPlugins, qCastAsText, qIndexSize);
53+
qAutoInc, qIndexVisible, qIndexInvisible, qGetAuthPlugins, qCastAsText, qIndexSize,
54+
qPgRolemanRolesList,
55+
qPgRolemanRoleLoad,
56+
qPgRolemanMembershipsLoad,
57+
qPgRolemanObjPrivsDatabasesLoad,
58+
qPgRolemanObjPrivsSchemasLoad,
59+
qPgRolemanObjPrivsTablesViewsLoad,
60+
qPgRolemanObjPrivsSequencesLoad,
61+
qPgRolemanObjPrivsRoutinesLoad,
62+
qPgRolemanDefaultPrivsLoad,
63+
qPgRolemanRoleCreate,
64+
qPgRolemanRoleAlter,
65+
qPgRolemanRolePasswordClear,
66+
qPgRolemanRolePasswordSet,
67+
qPgRolemanRoleDrop,
68+
qPgRolemanMembershipGrant,
69+
qPgRolemanMembershipRevoke,
70+
qPgRolemanMembershipRegrant,
71+
qPgRolemanObjPrivGrant,
72+
qPgRolemanObjPrivRevoke,
73+
qPgRolemanDefaultPrivGrant,
74+
qPgRolemanDefaultPrivRevoke
75+
);
5476
TSqlProvider = class
5577
strict protected
5678
FNetType: TNetType;

source/dbstructures.postgresql.pas

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,186 @@ function TPostgreSQLProvider.GetSql(AId: TQueryId): string;
745745
'ORDER BY UPPER(t.typname)',
746746
'' // ServerVersion < 9
747747
);
748+
749+
// PostgreSQL role manager
750+
qPgRolemanRolesList: Result :=
751+
'SELECT rolname ' +
752+
'FROM pg_catalog.pg_roles ' +
753+
'ORDER BY LOWER(rolname)';
754+
755+
qPgRolemanRoleLoad: Result :=
756+
'SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit ' +
757+
'FROM pg_catalog.pg_roles ' +
758+
'WHERE rolname = %s';
759+
760+
qPgRolemanMembershipsLoad: Result := IfThen(
761+
FServerVersion >= 160000,
762+
'SELECT all_roles.rolname AS member_of, ' +
763+
' (m.roleid IS NOT NULL) AS is_member, ' +
764+
' COALESCE(m.admin_option, FALSE) AS admin_option, ' +
765+
' COALESCE(m.inherit_option, TRUE) AS inherit_option, ' +
766+
' COALESCE(m.set_option, TRUE) AS set_option ' +
767+
'FROM pg_catalog.pg_roles AS target ' +
768+
'CROSS JOIN pg_catalog.pg_roles AS all_roles ' +
769+
'LEFT JOIN pg_catalog.pg_auth_members AS m ' +
770+
' ON m.member = target.oid ' +
771+
' AND m.roleid = all_roles.oid ' +
772+
'WHERE target.rolname = %s ' +
773+
' AND all_roles.rolname <> target.rolname ' +
774+
'ORDER BY LOWER(all_roles.rolname)',
775+
'SELECT all_roles.rolname AS member_of, ' +
776+
' (m.roleid IS NOT NULL) AS is_member, ' +
777+
' COALESCE(m.admin_option, FALSE) AS admin_option, ' +
778+
' TRUE AS inherit_option, ' +
779+
' TRUE AS set_option ' +
780+
'FROM pg_catalog.pg_roles AS target ' +
781+
'CROSS JOIN pg_catalog.pg_roles AS all_roles ' +
782+
'LEFT JOIN pg_catalog.pg_auth_members AS m ' +
783+
' ON m.member = target.oid ' +
784+
' AND m.roleid = all_roles.oid ' +
785+
'WHERE target.rolname = %s ' +
786+
' AND all_roles.rolname <> target.rolname ' +
787+
'ORDER BY LOWER(all_roles.rolname)'
788+
);
789+
790+
qPgRolemanObjPrivsDatabasesLoad: Result :=
791+
'SELECT current_database() AS schema_name, ' +
792+
' d.datname AS object_name, ' +
793+
' quote_ident(d.datname) AS object_identity, ' +
794+
' COALESCE(string_agg(DISTINCT x.privilege_type, '','' ORDER BY x.privilege_type), '''') AS privileges, ' +
795+
' COALESCE(string_agg(DISTINCT CASE WHEN x.is_grantable THEN x.privilege_type END, '','' ORDER BY CASE WHEN x.is_grantable THEN x.privilege_type END), '''') AS grantable ' +
796+
'FROM pg_catalog.pg_database AS d ' +
797+
'LEFT JOIN LATERAL aclexplode(COALESCE(d.datacl, acldefault(''d'', d.datdba))) AS x ON TRUE ' +
798+
'LEFT JOIN pg_catalog.pg_roles AS grantee ON grantee.oid = x.grantee ' +
799+
'WHERE grantee.rolname = %s ' +
800+
'GROUP BY d.datname ' +
801+
'ORDER BY LOWER(d.datname)';
802+
803+
qPgRolemanObjPrivsSchemasLoad: Result :=
804+
'SELECT n.nspname AS schema_name, ' +
805+
' n.nspname AS object_name, ' +
806+
' quote_ident(n.nspname) AS object_identity, ' +
807+
' COALESCE(string_agg(DISTINCT x.privilege_type, '','' ORDER BY x.privilege_type), '''') AS privileges, ' +
808+
' COALESCE(string_agg(DISTINCT CASE WHEN x.is_grantable THEN x.privilege_type END, '','' ORDER BY CASE WHEN x.is_grantable THEN x.privilege_type END), '''') AS grantable ' +
809+
'FROM pg_catalog.pg_namespace AS n ' +
810+
'LEFT JOIN LATERAL aclexplode(COALESCE(n.nspacl, acldefault(''n'', n.nspowner))) AS x ON TRUE ' +
811+
'LEFT JOIN pg_catalog.pg_roles AS grantee ON grantee.oid = x.grantee ' +
812+
'WHERE n.nspname NOT IN (''pg_catalog'', ''information_schema'') ' +
813+
' AND n.nspname NOT LIKE ''pg_toast%%'' ' +
814+
' AND grantee.rolname = %s ' +
815+
'GROUP BY n.nspname ' +
816+
'ORDER BY LOWER(n.nspname)';
817+
818+
qPgRolemanObjPrivsTablesViewsLoad: Result :=
819+
'SELECT n.nspname AS schema_name, ' +
820+
' c.relname AS object_name, ' +
821+
' quote_ident(n.nspname) || ''.'' || quote_ident(c.relname) AS object_identity, ' +
822+
' COALESCE(string_agg(DISTINCT x.privilege_type, '','' ORDER BY x.privilege_type), '''') AS privileges, ' +
823+
' COALESCE(string_agg(DISTINCT CASE WHEN x.is_grantable THEN x.privilege_type END, '','' ORDER BY CASE WHEN x.is_grantable THEN x.privilege_type END), '''') AS grantable ' +
824+
'FROM pg_catalog.pg_class AS c ' +
825+
'JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace ' +
826+
'LEFT JOIN LATERAL aclexplode(COALESCE(c.relacl, acldefault(''r'', c.relowner))) AS x ON TRUE ' +
827+
'LEFT JOIN pg_catalog.pg_roles AS grantee ON grantee.oid = x.grantee ' +
828+
'WHERE c.relkind IN (''r'', ''v'', ''m'', ''f'', ''p'') ' +
829+
' AND n.nspname NOT IN (''pg_catalog'', ''information_schema'') ' +
830+
' AND n.nspname NOT LIKE ''pg_toast%%'' ' +
831+
' AND grantee.rolname = %s ' +
832+
'GROUP BY n.nspname, c.relname ' +
833+
'ORDER BY LOWER(n.nspname), LOWER(c.relname)';
834+
835+
qPgRolemanObjPrivsSequencesLoad: Result :=
836+
'SELECT n.nspname AS schema_name, ' +
837+
' c.relname AS object_name, ' +
838+
' quote_ident(n.nspname) || ''.'' || quote_ident(c.relname) AS object_identity, ' +
839+
' COALESCE(string_agg(DISTINCT x.privilege_type, '','' ORDER BY x.privilege_type), '''') AS privileges, ' +
840+
' COALESCE(string_agg(DISTINCT CASE WHEN x.is_grantable THEN x.privilege_type END, '','' ORDER BY CASE WHEN x.is_grantable THEN x.privilege_type END), '''') AS grantable ' +
841+
'FROM pg_catalog.pg_class AS c ' +
842+
'JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace ' +
843+
'LEFT JOIN LATERAL aclexplode(COALESCE(c.relacl, acldefault(''S'', c.relowner))) AS x ON TRUE ' +
844+
'LEFT JOIN pg_catalog.pg_roles AS grantee ON grantee.oid = x.grantee ' +
845+
'WHERE c.relkind = ''S'' ' +
846+
' AND n.nspname NOT IN (''pg_catalog'', ''information_schema'') ' +
847+
' AND n.nspname NOT LIKE ''pg_toast%%'' ' +
848+
' AND grantee.rolname = %s ' +
849+
'GROUP BY n.nspname, c.relname ' +
850+
'ORDER BY LOWER(n.nspname), LOWER(c.relname)';
851+
852+
qPgRolemanObjPrivsRoutinesLoad: Result :=
853+
'SELECT n.nspname AS schema_name, ' +
854+
' p.proname AS object_name, ' +
855+
' quote_ident(n.nspname) || ''.'' || quote_ident(p.proname) || ''('' || pg_catalog.pg_get_function_identity_arguments(p.oid) || '')'' AS object_identity, ' +
856+
' COALESCE(string_agg(DISTINCT x.privilege_type, '','' ORDER BY x.privilege_type), '''') AS privileges, ' +
857+
' COALESCE(string_agg(DISTINCT CASE WHEN x.is_grantable THEN x.privilege_type END, '','' ORDER BY CASE WHEN x.is_grantable THEN x.privilege_type END), '''') AS grantable ' +
858+
'FROM pg_catalog.pg_proc AS p ' +
859+
'JOIN pg_catalog.pg_namespace AS n ON n.oid = p.pronamespace ' +
860+
'LEFT JOIN LATERAL aclexplode(COALESCE(p.proacl, acldefault(''f'', p.proowner))) AS x ON TRUE ' +
861+
'LEFT JOIN pg_catalog.pg_roles AS grantee ON grantee.oid = x.grantee ' +
862+
'WHERE n.nspname NOT IN (''pg_catalog'', ''information_schema'') ' +
863+
' AND n.nspname NOT LIKE ''pg_toast%%'' ' +
864+
' AND grantee.rolname = %s ' +
865+
'GROUP BY n.nspname, p.proname, p.oid ' +
866+
'ORDER BY LOWER(n.nspname), LOWER(p.proname), p.oid';
867+
868+
qPgRolemanDefaultPrivsLoad: Result :=
869+
'SELECT owner_role.rolname AS owner_name, ' +
870+
' COALESCE(n.nspname, '''') AS schema_name, ' +
871+
' CASE d.defaclobjtype WHEN ''r'' THEN ''TABLES'' WHEN ''S'' THEN ''SEQUENCES'' WHEN ''f'' THEN ''ROUTINES'' ELSE d.defaclobjtype::text END AS kind, ' +
872+
' grantee_role.rolname AS grantee, ' +
873+
' COALESCE(string_agg(DISTINCT x.privilege_type, '','' ORDER BY x.privilege_type), '''') AS privileges ' +
874+
'FROM pg_catalog.pg_default_acl AS d ' +
875+
'JOIN pg_catalog.pg_roles AS owner_role ON owner_role.oid = d.defaclrole ' +
876+
'LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = d.defaclnamespace ' +
877+
'LEFT JOIN LATERAL aclexplode(d.defaclacl) AS x ON TRUE ' +
878+
'LEFT JOIN pg_catalog.pg_roles AS grantee_role ON grantee_role.oid = x.grantee ' +
879+
'WHERE grantee_role.rolname = %s ' +
880+
'GROUP BY owner_role.rolname, n.nspname, d.defaclobjtype, grantee_role.rolname ' +
881+
'ORDER BY LOWER(owner_role.rolname), LOWER(COALESCE(n.nspname, '''')), d.defaclobjtype';
882+
883+
qPgRolemanRoleCreate: Result :=
884+
'CREATE ROLE %s WITH %s %s %s %s %s %s %s CONNECTION LIMIT %d';
885+
886+
qPgRolemanRoleAlter: Result :=
887+
'DO $$ BEGIN ' +
888+
'IF %s <> %s THEN EXECUTE ''ALTER ROLE '' || %s || '' RENAME TO '' || %s; END IF; ' +
889+
'END $$; ' +
890+
'ALTER ROLE %s WITH %s %s %s %s %s %s %s CONNECTION LIMIT %d';
891+
892+
qPgRolemanRolePasswordClear: Result :=
893+
'ALTER ROLE %s PASSWORD NULL';
894+
895+
qPgRolemanRolePasswordSet: Result :=
896+
'ALTER ROLE %s PASSWORD %s';
897+
898+
qPgRolemanRoleDrop: Result :=
899+
'DROP ROLE %s';
900+
901+
qPgRolemanMembershipGrant: Result := IfThen(
902+
FServerVersion >= 160000,
903+
'GRANT %s TO %s WITH %s, %s, %s',
904+
'GRANT %s TO %s%s'
905+
);
906+
907+
qPgRolemanMembershipRevoke: Result :=
908+
'REVOKE %s FROM %s';
909+
910+
qPgRolemanMembershipRegrant: Result := IfThen(
911+
FServerVersion >= 160000,
912+
'REVOKE %s FROM %s; GRANT %s TO %s WITH %s, %s, %s',
913+
'REVOKE %s FROM %s; GRANT %s TO %s%s'
914+
);
915+
916+
qPgRolemanObjPrivGrant: Result :=
917+
'GRANT %s ON %s %s TO %s%s';
918+
919+
qPgRolemanObjPrivRevoke: Result :=
920+
'REVOKE %s ON %s %s FROM %s';
921+
922+
qPgRolemanDefaultPrivGrant: Result :=
923+
'ALTER DEFAULT PRIVILEGES FOR ROLE %s%s GRANT %s ON %s TO %s%s';
924+
925+
qPgRolemanDefaultPrivRevoke: Result :=
926+
'ALTER DEFAULT PRIVILEGES FOR ROLE %s%s REVOKE %s ON %s FROM %s';
927+
748928
qAutoInc: Result := 'SERIAL';
749929
qCastAsText: Result := '%s::text';
750930
else Result := inherited;

source/main.pas

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,7 +1471,7 @@ implementation
14711471

14721472
uses
14731473
FileInfo, winpeimagereader, elfreader, machoreader, About, data_sorting, column_selection, loaddata, editvar,
1474-
copytable, csv_detector, exportgrid, usermanager, reformatter, connections, sqlhelp, updatecheck,
1474+
copytable, csv_detector, exportgrid, usermanager, rolemanagerpg, reformatter, connections, sqlhelp, updatecheck,
14751475
insertfiles, texteditor, preferences, table_editor, view, routine_editor, trigger_editor, event_editor, grideditlinks,
14761476
crashdialog;
14771477

@@ -2828,9 +2828,16 @@ procedure TMainForm.EditorCommandOnClick(Sender: TObject);
28282828

28292829
procedure TMainForm.actUserManagerExecute(Sender: TObject);
28302830
var
2831-
Dialog: TUserManagerForm;
2831+
Dialog: TForm;
28322832
begin
2833-
Dialog := TUserManagerForm.Create(Self);
2833+
Dialog := nil;
2834+
case ActiveConnection.Parameters.NetTypeGroup of
2835+
ngMySQL: Dialog := TUserManagerForm.Create(Self);
2836+
ngPgSQL: Dialog := TRoleManagerPgForm.Create(Self);
2837+
else ErrorDialog(ActiveConnection.Parameters.NetTypeName(False) + ' not supported for managing users');
2838+
end;
2839+
if Dialog = nil then
2840+
Exit;
28342841
Dialog.ShowModal;
28352842
Dialog.Free;
28362843
end;

0 commit comments

Comments
 (0)