Skip to content

Commit

Permalink
sql: implement pg_has_role
Browse files Browse the repository at this point in the history
Needed for cockroachdb#69010.

This commit implements the `pg_has_role` builtin function. `pg_has_role`
returns whether the user has privileges for a specified role or not.
Allowable privilege types are MEMBER and USAGE. MEMBER denotes direct or
indirect membership in the role (that is, the right to do SET ROLE),
while USAGE denotes whether the privileges of the role are immediately
available without doing SET ROLE.

`pg_has_role` was the last remaining unimplemented "access privilege
inquiry functions", and was omitted from 94c25be because our role-based
access control system was not mature enough to support it at the time.

The commit also makes a small modification to `pg_catalog.pg_roles` and
`pg_catalog.pg_authid` to reflect that fact that all users and roles
inherit the privileges of roles they are members of.

Release note (sql change): The pg_has_role builtin function is now
supported, which returns whether a given user has privileges for a
specified role or not.

Release justification: None, waiting for v22.1.
  • Loading branch information
nvanbenschoten committed Sep 15, 2021
1 parent 25311f9 commit 3f2007d
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 11 deletions.
12 changes: 12 additions & 0 deletions docs/generated/sql/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3138,6 +3138,18 @@ SELECT * FROM crdb_internal.check_consistency(true, ‘\x02’, ‘\x04’)</p>
</span></td></tr>
<tr><td><a name="pg_get_serial_sequence"></a><code>pg_get_serial_sequence(table_name: <a href="string.html">string</a>, column_name: <a href="string.html">string</a>) &rarr; <a href="string.html">string</a></code></td><td><span class="funcdesc"><p>Returns the name of the sequence used by the given column_name in the table table_name.</p>
</span></td></tr>
<tr><td><a name="pg_has_role"></a><code>pg_has_role(role: <a href="string.html">string</a>, privilege: <a href="string.html">string</a>) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns whether or not the current user has privileges for role.</p>
</span></td></tr>
<tr><td><a name="pg_has_role"></a><code>pg_has_role(role: oid, privilege: <a href="string.html">string</a>) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns whether or not the current user has privileges for role.</p>
</span></td></tr>
<tr><td><a name="pg_has_role"></a><code>pg_has_role(user: <a href="string.html">string</a>, role: <a href="string.html">string</a>, privilege: <a href="string.html">string</a>) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns whether or not the user has privileges for role.</p>
</span></td></tr>
<tr><td><a name="pg_has_role"></a><code>pg_has_role(user: <a href="string.html">string</a>, role: oid, privilege: <a href="string.html">string</a>) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns whether or not the user has privileges for role.</p>
</span></td></tr>
<tr><td><a name="pg_has_role"></a><code>pg_has_role(user: oid, role: <a href="string.html">string</a>, privilege: <a href="string.html">string</a>) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns whether or not the user has privileges for role.</p>
</span></td></tr>
<tr><td><a name="pg_has_role"></a><code>pg_has_role(user: oid, role: oid, privilege: <a href="string.html">string</a>) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns whether or not the user has privileges for role.</p>
</span></td></tr>
<tr><td><a name="pg_is_other_temp_schema"></a><code>pg_is_other_temp_schema(oid: oid) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns true if the given OID is the OID of another session’s temporary schema. (This can be useful, for example, to exclude other sessions’ temporary tables from a catalog display.)</p>
</span></td></tr>
<tr><td><a name="pg_my_temp_schema"></a><code>pg_my_temp_schema() &rarr; oid</code></td><td><span class="funcdesc"><p>Returns the OID of the current session’s temporary schema, or zero if it has none (because it has not created any temporary tables).</p>
Expand Down
7 changes: 7 additions & 0 deletions pkg/sql/faketreeeval/evalctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ func (ep *DummyEvalPlanner) UnsafeDeleteNamespaceEntry(
return errors.WithStack(errEvalPlanner)
}

// UserHasAdminRole is part of the EvalPlanner interface.
func (ep *DummyEvalPlanner) UserHasAdminRole(
ctx context.Context, user security.SQLUsername,
) (bool, error) {
return false, errors.WithStack(errEvalPlanner)
}

// MemberOfWithAdminOption is part of the EvalPlanner interface.
func (ep *DummyEvalPlanner) MemberOfWithAdminOption(
ctx context.Context, member security.SQLUsername,
Expand Down
14 changes: 7 additions & 7 deletions pkg/sql/logictest/testdata/logic_test/pg_catalog
Original file line number Diff line number Diff line change
Expand Up @@ -3524,8 +3524,8 @@ ORDER BY rolname
----
oid rolname rolsuper rolinherit rolcreaterole rolcreatedb rolcatupdate rolcanlogin rolreplication
2310524507 admin true true true true false true false
1546506610 root true false true true false true false
2264919399 testuser false false false false false true false
1546506610 root true true true true false true false
2264919399 testuser false true false false false true false

query OTITTBT colnames
SELECT oid, rolname, rolconnlimit, rolpassword, rolvaliduntil, rolbypassrls, rolconfig
Expand Down Expand Up @@ -5266,8 +5266,8 @@ ORDER BY rolname
----
rolname rolcreatedb rolconfig rolinherit rolcanlogin rolvaliduntil
testrole1 false NULL true false NULL
testuser1 true {timezone=America/Los_Angeles,application_name=a} false true NULL
testuser2 false NULL false true 3022-01-01 00:00:00 +0000 UTC
testuser1 true {timezone=America/Los_Angeles,application_name=a} true true NULL
testuser2 false NULL true true 3022-01-01 00:00:00 +0000 UTC

query TBBBBT colnames
SELECT rolname, rolcreatedb, rolcreaterole, rolinherit, rolcanlogin, rolvaliduntil
Expand All @@ -5276,10 +5276,10 @@ WHERE rolname IN ('testuser1', 'testuser2', 'testrole1', 'root')
ORDER BY rolname
----
rolname rolcreatedb rolcreaterole rolinherit rolcanlogin rolvaliduntil
root true true false true NULL
root true true true true NULL
testrole1 false false true false NULL
testuser1 true false false true NULL
testuser2 false true false true 3022-01-01 00:00:00 +0000 UTC
testuser1 true false true true NULL
testuser2 false true true true 3022-01-01 00:00:00 +0000 UTC

# Testing users that have admin role

Expand Down
151 changes: 151 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/privilege_builtins
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,157 @@ SELECT has_type_privilege('bar', 'text'::REGTYPE::OID, 'USAGE')
----
true


## pg_has_role

query BBBBBB
SELECT pg_has_role(12345, 'USAGE'),
pg_has_role(12345, 'USAGE WITH GRANT OPTION'),
pg_has_role(12345, 'USAGE WITH ADMIN OPTION'),
pg_has_role(12345, 'MEMBER'),
pg_has_role(12345, 'MEMBER WITH GRANT OPTION'),
pg_has_role(12345, 'MEMBER WITH ADMIN OPTION')
----
NULL NULL NULL NULL NULL NULL

query BBBBBB
SELECT pg_has_role((SELECT oid FROM pg_roles WHERE rolname = 'root'), 'USAGE'),
pg_has_role((SELECT oid FROM pg_roles WHERE rolname = 'root'), 'USAGE WITH GRANT OPTION'),
pg_has_role((SELECT oid FROM pg_roles WHERE rolname = 'root'), 'USAGE WITH ADMIN OPTION'),
pg_has_role((SELECT oid FROM pg_roles WHERE rolname = 'root'), 'MEMBER'),
pg_has_role((SELECT oid FROM pg_roles WHERE rolname = 'root'), 'MEMBER WITH GRANT OPTION'),
pg_has_role((SELECT oid FROM pg_roles WHERE rolname = 'root'), 'MEMBER WITH ADMIN OPTION')
----
true true true true true true

query error pgcode 42704 role 'does_not_exist' does not exist
SELECT pg_has_role('does_not_exist', 'USAGE')

query BBBBBB
SELECT pg_has_role('root', 'USAGE'),
pg_has_role('root', 'USAGE WITH GRANT OPTION'),
pg_has_role('root', 'USAGE WITH ADMIN OPTION'),
pg_has_role('root', 'MEMBER'),
pg_has_role('root', 'MEMBER WITH GRANT OPTION'),
pg_has_role('root', 'MEMBER WITH ADMIN OPTION')
----
true true true true true true

query error pgcode 22023 unrecognized privilege type: "SELECT"
SELECT pg_has_role('root', 'SELECT')

query error pgcode 22023 unrecognized privilege type: "SELECT"
SELECT pg_has_role('root', 'SELECT, SELECT')

query error pgcode 42704 role 'no_user' does not exist
SELECT pg_has_role('no_user', 'root', 'USAGE')

query BBBBBB
SELECT pg_has_role('root', 'root', 'USAGE'),
pg_has_role('root', 'root', 'USAGE WITH GRANT OPTION'),
pg_has_role('root', 'root', 'USAGE WITH ADMIN OPTION'),
pg_has_role('root', 'root', 'MEMBER'),
pg_has_role('root', 'root', 'MEMBER WITH GRANT OPTION'),
pg_has_role('root', 'root', 'MEMBER WITH ADMIN OPTION')
----
true true true true true true

query BBBBBB
SELECT pg_has_role('bar', 'root', 'USAGE'),
pg_has_role('bar', 'root', 'USAGE WITH GRANT OPTION'),
pg_has_role('bar', 'root', 'USAGE WITH ADMIN OPTION'),
pg_has_role('bar', 'root', 'MEMBER'),
pg_has_role('bar', 'root', 'MEMBER WITH GRANT OPTION'),
pg_has_role('bar', 'root', 'MEMBER WITH ADMIN OPTION')
----
false false false false false false

statement ok
CREATE ROLE some_users

statement ok
GRANT some_users TO bar

query BBBBBB
SELECT pg_has_role('testuser', 'some_users', 'USAGE'),
pg_has_role('testuser', 'some_users', 'USAGE WITH GRANT OPTION'),
pg_has_role('testuser', 'some_users', 'USAGE WITH ADMIN OPTION'),
pg_has_role('testuser', 'some_users', 'MEMBER'),
pg_has_role('testuser', 'some_users', 'MEMBER WITH GRANT OPTION'),
pg_has_role('testuser', 'some_users', 'MEMBER WITH ADMIN OPTION')
----
false false false false false false

query BBBBBB
SELECT pg_has_role('root', 'some_users', 'USAGE'),
pg_has_role('root', 'some_users', 'USAGE WITH GRANT OPTION'),
pg_has_role('root', 'some_users', 'USAGE WITH ADMIN OPTION'),
pg_has_role('root', 'some_users', 'MEMBER'),
pg_has_role('root', 'some_users', 'MEMBER WITH GRANT OPTION'),
pg_has_role('root', 'some_users', 'MEMBER WITH ADMIN OPTION')
----
true true true true true true

query BBBBBB
SELECT pg_has_role('bar', 'some_users', 'USAGE'),
pg_has_role('bar', 'some_users', 'USAGE WITH GRANT OPTION'),
pg_has_role('bar', 'some_users', 'USAGE WITH ADMIN OPTION'),
pg_has_role('bar', 'some_users', 'MEMBER'),
pg_has_role('bar', 'some_users', 'MEMBER WITH GRANT OPTION'),
pg_has_role('bar', 'some_users', 'MEMBER WITH ADMIN OPTION')
----
true false false true false false

statement ok
GRANT some_users TO bar WITH ADMIN OPTION

query BBBBBB
SELECT pg_has_role('bar', 'some_users', 'USAGE'),
pg_has_role('bar', 'some_users', 'USAGE WITH GRANT OPTION'),
pg_has_role('bar', 'some_users', 'USAGE WITH ADMIN OPTION'),
pg_has_role('bar', 'some_users', 'MEMBER'),
pg_has_role('bar', 'some_users', 'MEMBER WITH GRANT OPTION'),
pg_has_role('bar', 'some_users', 'MEMBER WITH ADMIN OPTION')
----
true true true true true true

# WITH ADMIN OPTION is session-user dependent.

query BBBBBB
SELECT pg_has_role('testuser', 'testuser', 'USAGE'),
pg_has_role('testuser', 'testuser', 'USAGE WITH GRANT OPTION'),
pg_has_role('testuser', 'testuser', 'USAGE WITH ADMIN OPTION'),
pg_has_role('testuser', 'testuser', 'MEMBER'),
pg_has_role('testuser', 'testuser', 'MEMBER WITH GRANT OPTION'),
pg_has_role('testuser', 'testuser', 'MEMBER WITH ADMIN OPTION')
----
true false false true false false

user testuser

query BBBBBB
SELECT pg_has_role('testuser', 'testuser', 'USAGE'),
pg_has_role('testuser', 'testuser', 'USAGE WITH GRANT OPTION'),
pg_has_role('testuser', 'testuser', 'USAGE WITH ADMIN OPTION'),
pg_has_role('testuser', 'testuser', 'MEMBER'),
pg_has_role('testuser', 'testuser', 'MEMBER WITH GRANT OPTION'),
pg_has_role('testuser', 'testuser', 'MEMBER WITH ADMIN OPTION')
----
true true true true true true

query BBBBBB
SELECT pg_has_role('testuser', 'USAGE'),
pg_has_role('testuser', 'USAGE WITH GRANT OPTION'),
pg_has_role('testuser', 'USAGE WITH ADMIN OPTION'),
pg_has_role('testuser', 'MEMBER'),
pg_has_role('testuser', 'MEMBER WITH GRANT OPTION'),
pg_has_role('testuser', 'MEMBER WITH ADMIN OPTION')
----
true true true true true true

user root


# Regression test for #39703.

statement ok
Expand Down
12 changes: 8 additions & 4 deletions pkg/sql/pg_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,9 @@ https://www.postgresql.org/docs/9.5/catalog-pg-authid.html`,
h := makeOidHasher()
return forEachRole(ctx, p, func(username security.SQLUsername, isRole bool, options roleOptions, _ tree.Datum) error {
isRoot := tree.DBool(username.IsRootUser() || username.IsAdminRole())
isRoleDBool := tree.DBool(isRole)
// Currently, all users and roles inherit the privileges of roles they are
// members of. See https://github.com/cockroachdb/cockroach/issues/69583.
roleInherits := tree.DBool(true)
noLogin, err := options.noLogin()
if err != nil {
return err
Expand Down Expand Up @@ -526,7 +528,7 @@ https://www.postgresql.org/docs/9.5/catalog-pg-authid.html`,
h.UserOid(username), // oid
tree.NewDName(username.Normalized()), // rolname
tree.MakeDBool(isRoot || isSuper), // rolsuper
tree.MakeDBool(isRoleDBool), // rolinherit. Roles inherit by default.
tree.MakeDBool(roleInherits), // rolinherit
tree.MakeDBool(isRoot || createRole), // rolcreaterole
tree.MakeDBool(isRoot || createDB), // rolcreatedb
tree.MakeDBool(roleCanLogin), // rolcanlogin.
Expand Down Expand Up @@ -2343,7 +2345,9 @@ https://www.postgresql.org/docs/9.5/view-pg-roles.html`,
return forEachRole(ctx, p,
func(username security.SQLUsername, isRole bool, options roleOptions, settings tree.Datum) error {
isRoot := tree.DBool(username.IsRootUser() || username.IsAdminRole())
isRoleDBool := tree.DBool(isRole)
// Currently, all users and roles inherit the privileges of roles they are
// members of. See https://github.com/cockroachdb/cockroach/issues/69583.
roleInherits := tree.DBool(true)
noLogin, err := options.noLogin()
if err != nil {
return err
Expand All @@ -2370,7 +2374,7 @@ https://www.postgresql.org/docs/9.5/view-pg-roles.html`,
h.UserOid(username), // oid
tree.NewDName(username.Normalized()), // rolname
tree.MakeDBool(isRoot || isSuper), // rolsuper
tree.MakeDBool(isRoleDBool), // rolinherit. Roles inherit by default.
tree.MakeDBool(roleInherits), // rolinherit
tree.MakeDBool(isRoot || createRole), // rolcreaterole
tree.MakeDBool(isRoot || createDB), // rolcreatedb
tree.DBoolFalse, // rolcatupdate
Expand Down
Loading

0 comments on commit 3f2007d

Please sign in to comment.