diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 35aec6d3ce8..182e58353f1 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1883,6 +1883,7 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g + \drds [ role-pattern [ database-pattern ] ] @@ -1905,6 +1906,27 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g + + + \drg[S] [ pattern ] + + + Lists information about each granted role membership, including + assigned options (ADMIN, + INHERIT and/or SET) and grantor. + See the GRANT + command for information about role memberships. + + + By default, only grants to user-created roles are shown; supply the + S modifier to include system roles. + If pattern is specified, + only grants to those roles whose names match the pattern are listed. + + + + + \dRp[+] [ pattern ] diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 511debbe814..6733f008fd5 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -918,6 +918,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd) free(pattern2); } + else if (cmd[2] == 'g') + success = describeRoleGrants(pattern, show_system); else status = PSQL_CMD_UNKNOWN; break; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 9325a46b8fd..45f6a86b872 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3617,7 +3617,7 @@ describeRoles(const char *pattern, bool verbose, bool showSystem) PGresult *res; printTableContent cont; printTableOpt myopt = pset.popt.topt; - int ncols = 3; + int ncols = 2; int nrows = 0; int i; int conns; @@ -3631,11 +3631,7 @@ describeRoles(const char *pattern, bool verbose, bool showSystem) printfPQExpBuffer(&buf, "SELECT r.rolname, r.rolsuper, r.rolinherit,\n" " r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,\n" - " r.rolconnlimit, r.rolvaliduntil,\n" - " ARRAY(SELECT b.rolname\n" - " FROM pg_catalog.pg_auth_members m\n" - " JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n" - " WHERE m.member = r.oid) as memberof"); + " r.rolconnlimit, r.rolvaliduntil"); if (verbose) { @@ -3675,8 +3671,6 @@ describeRoles(const char *pattern, bool verbose, bool showSystem) printTableAddHeader(&cont, gettext_noop("Role name"), true, align); printTableAddHeader(&cont, gettext_noop("Attributes"), true, align); - /* ignores implicit memberships from superuser & pg_database_owner */ - printTableAddHeader(&cont, gettext_noop("Member of"), true, align); if (verbose) printTableAddHeader(&cont, gettext_noop("Description"), true, align); @@ -3701,11 +3695,11 @@ describeRoles(const char *pattern, bool verbose, bool showSystem) if (strcmp(PQgetvalue(res, i, 5), "t") != 0) add_role_attribute(&buf, _("Cannot login")); - if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0) + if (strcmp(PQgetvalue(res, i, (verbose ? 9 : 8)), "t") == 0) add_role_attribute(&buf, _("Replication")); if (pset.sversion >= 90500) - if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0) + if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0) add_role_attribute(&buf, _("Bypass RLS")); conns = atoi(PQgetvalue(res, i, 6)); @@ -3735,10 +3729,8 @@ describeRoles(const char *pattern, bool verbose, bool showSystem) printTableAddCell(&cont, attr[i], false, false); - printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false); - if (verbose) - printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false); } termPQExpBuffer(&buf); @@ -3831,6 +3823,75 @@ error_return: return false; } +/* + * \drg + * Describes role grants. + */ +bool +describeRoleGrants(const char *pattern, bool showSystem) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT m.rolname AS \"%s\", r.rolname AS \"%s\",\n" + " pg_catalog.concat_ws(', ',\n", + gettext_noop("Role name"), + gettext_noop("Member of")); + + if (pset.sversion >= 160000) + appendPQExpBufferStr(&buf, + " CASE WHEN pam.admin_option THEN 'ADMIN' END,\n" + " CASE WHEN pam.inherit_option THEN 'INHERIT' END,\n" + " CASE WHEN pam.set_option THEN 'SET' END\n"); + else + appendPQExpBufferStr(&buf, + " CASE WHEN pam.admin_option THEN 'ADMIN' END,\n" + " CASE WHEN m.rolinherit THEN 'INHERIT' END,\n" + " 'SET'\n"); + + appendPQExpBuffer(&buf, + " ) AS \"%s\",\n" + " g.rolname AS \"%s\"\n", + gettext_noop("Options"), + gettext_noop("Grantor")); + + appendPQExpBufferStr(&buf, + "FROM pg_catalog.pg_roles m\n" + " JOIN pg_catalog.pg_auth_members pam ON (pam.member = m.oid)\n" + " LEFT JOIN pg_catalog.pg_roles r ON (pam.roleid = r.oid)\n" + " LEFT JOIN pg_catalog.pg_roles g ON (pam.grantor = g.oid)\n"); + + if (!showSystem && !pattern) + appendPQExpBufferStr(&buf, "WHERE m.rolname !~ '^pg_'\n"); + + if (!validateSQLNamePattern(&buf, pattern, false, false, + NULL, "m.rolname", NULL, NULL, + NULL, 1)) + { + termPQExpBuffer(&buf); + return false; + } + + appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;\n"); + + res = PSQLexec(buf.data); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of role grants"); + myopt.translate_header = true; + + printQuery(res, &myopt, pset.queryFout, false, pset.logfile); + + PQclear(res); + return true; +} + /* * listTables() diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 554fe867255..24c0884a347 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -37,6 +37,9 @@ extern bool describeRoles(const char *pattern, bool verbose, bool showSystem); /* \drds */ extern bool listDbRoleSettings(const char *pattern, const char *pattern2); +/* \drg */ +extern bool describeRoleGrants(const char *pattern, bool showSystem); + /* \z (or \dp) */ extern bool permissionsList(const char *pattern, bool showSystem); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 0ff595e7eef..b2b749d69a5 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -280,6 +280,7 @@ slashUsage(unsigned short int pager) HELP0(" \\dp[S] [PATTERN] list table, view, and sequence access privileges\n"); HELP0(" \\dP[itn+] [PATTERN] list [only index/table] partitioned relations [n=nested]\n"); HELP0(" \\drds [ROLEPTRN [DBPTRN]] list per-database role settings\n"); + HELP0(" \\drg[S] [PATTERN] list role grants\n"); HELP0(" \\dRp[+] [PATTERN] list replication publications\n"); HELP0(" \\dRs[+] [PATTERN] list replication subscriptions\n"); HELP0(" \\ds[S+] [PATTERN] list sequences\n"); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index e9fddd91ebf..779fdc90cbc 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1713,7 +1713,7 @@ psql_completion(const char *text, int start, int end) "\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df", "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL", "\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt", - "\\drds", "\\dRs", "\\dRp", "\\ds", + "\\drds", "\\drg", "\\dRs", "\\dRp", "\\ds", "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy", "\\echo", "\\edit", "\\ef", "\\elif", "\\else", "\\encoding", "\\endif", "\\errverbose", "\\ev", @@ -4760,7 +4760,9 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); else if (TailMatchesCS("\\dT*")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); - else if (TailMatchesCS("\\du*") || TailMatchesCS("\\dg*")) + else if (TailMatchesCS("\\du*") || + TailMatchesCS("\\dg*") || + TailMatchesCS("\\drg*")) COMPLETE_WITH_QUERY(Query_for_list_of_roles); else if (TailMatchesCS("\\dv*")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views); diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index c062c3dc7b5..7cd0c27cca8 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -6187,9 +6187,9 @@ List of text search templates (0 rows) \dg "no.such.role" - List of roles - Role name | Attributes | Member of ------------+------------+----------- + List of roles + Role name | Attributes +-----------+------------ \dL "no.such.language" List of languages @@ -6618,3 +6618,43 @@ cross-database references are not implemented: "no.such.database"."no.such.schem cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.data.type" \dX "no.such.database"."no.such.schema"."no.such.extended.statistics" cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.extended.statistics" +-- check \drg and \du +CREATE ROLE regress_du_role0; +CREATE ROLE regress_du_role1; +CREATE ROLE regress_du_role2; +CREATE ROLE regress_du_admin; +GRANT regress_du_role0 TO regress_du_admin WITH ADMIN TRUE; +GRANT regress_du_role1 TO regress_du_admin WITH ADMIN TRUE; +GRANT regress_du_role2 TO regress_du_admin WITH ADMIN TRUE; +GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN TRUE, INHERIT TRUE, SET TRUE GRANTED BY regress_du_admin; +GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN TRUE, INHERIT FALSE, SET FALSE GRANTED BY regress_du_admin; +GRANT regress_du_role1 TO regress_du_role2 WITH ADMIN TRUE , INHERIT FALSE, SET TRUE GRANTED BY regress_du_admin; +GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT TRUE, SET FALSE GRANTED BY regress_du_role1; +GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT TRUE , SET TRUE GRANTED BY regress_du_role1; +GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT FALSE, SET TRUE GRANTED BY regress_du_role2; +GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT FALSE, SET FALSE GRANTED BY regress_du_role2; +\drg regress_du_role* + List of role grants + Role name | Member of | Options | Grantor +------------------+------------------+---------------------+------------------ + regress_du_role1 | regress_du_role0 | ADMIN, INHERIT, SET | regress_du_admin + regress_du_role1 | regress_du_role0 | INHERIT | regress_du_role1 + regress_du_role1 | regress_du_role0 | SET | regress_du_role2 + regress_du_role2 | regress_du_role0 | ADMIN | regress_du_admin + regress_du_role2 | regress_du_role0 | INHERIT, SET | regress_du_role1 + regress_du_role2 | regress_du_role0 | | regress_du_role2 + regress_du_role2 | regress_du_role1 | ADMIN, SET | regress_du_admin +(7 rows) + +\du regress_du_role* + List of roles + Role name | Attributes +------------------+-------------- + regress_du_role0 | Cannot login + regress_du_role1 | Cannot login + regress_du_role2 | Cannot login + +DROP ROLE regress_du_role0; +DROP ROLE regress_du_role1; +DROP ROLE regress_du_role2; +DROP ROLE regress_du_admin; diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 965021fd84d..f3bc6cd07e8 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1823,3 +1823,29 @@ DROP FUNCTION psql_error; \dP "no.such.database"."no.such.schema"."no.such.partitioned.relation" \dT "no.such.database"."no.such.schema"."no.such.data.type" \dX "no.such.database"."no.such.schema"."no.such.extended.statistics" + +-- check \drg and \du +CREATE ROLE regress_du_role0; +CREATE ROLE regress_du_role1; +CREATE ROLE regress_du_role2; +CREATE ROLE regress_du_admin; + +GRANT regress_du_role0 TO regress_du_admin WITH ADMIN TRUE; +GRANT regress_du_role1 TO regress_du_admin WITH ADMIN TRUE; +GRANT regress_du_role2 TO regress_du_admin WITH ADMIN TRUE; + +GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN TRUE, INHERIT TRUE, SET TRUE GRANTED BY regress_du_admin; +GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN TRUE, INHERIT FALSE, SET FALSE GRANTED BY regress_du_admin; +GRANT regress_du_role1 TO regress_du_role2 WITH ADMIN TRUE , INHERIT FALSE, SET TRUE GRANTED BY regress_du_admin; +GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT TRUE, SET FALSE GRANTED BY regress_du_role1; +GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT TRUE , SET TRUE GRANTED BY regress_du_role1; +GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT FALSE, SET TRUE GRANTED BY regress_du_role2; +GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT FALSE, SET FALSE GRANTED BY regress_du_role2; + +\drg regress_du_role* +\du regress_du_role* + +DROP ROLE regress_du_role0; +DROP ROLE regress_du_role1; +DROP ROLE regress_du_role2; +DROP ROLE regress_du_admin;