Skip to content

Commit 322d3bb

Browse files
craig[bot]rafiss
andcommitted
Merge #149051
149051: builtins: add has_system_privilege builtin function r=rafiss a=rafiss This builtin function can be used to check for a system privilege using a SQL interface. Epic: None Release note (sql change): Added the has_system_privilege builtin function, which can be used to check if a user has the given system privilege. Co-authored-by: Rafi Shamim <[email protected]>
2 parents d0365c2 + e5d5d49 commit 322d3bb

File tree

6 files changed

+234
-9
lines changed

6 files changed

+234
-9
lines changed

docs/generated/sql/functions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3502,6 +3502,12 @@ may increase either contention or retry errors, or both.</p>
35023502
</span></td><td>Stable</td></tr>
35033503
<tr><td><a name="has_server_privilege"></a><code>has_server_privilege(user: oid, server: 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 foreign server.</p>
35043504
</span></td><td>Stable</td></tr>
3505+
<tr><td><a name="has_system_privilege"></a><code>has_system_privilege(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 system.</p>
3506+
</span></td><td>Stable</td></tr>
3507+
<tr><td><a name="has_system_privilege"></a><code>has_system_privilege(user: <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 system.</p>
3508+
</span></td><td>Stable</td></tr>
3509+
<tr><td><a name="has_system_privilege"></a><code>has_system_privilege(user: 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 system.</p>
3510+
</span></td><td>Stable</td></tr>
35053511
<tr><td><a name="has_table_privilege"></a><code>has_table_privilege(table: <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 table.</p>
35063512
</span></td><td>Stable</td></tr>
35073513
<tr><td><a name="has_table_privilege"></a><code>has_table_privilege(table: 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 table.</p>

pkg/sql/logictest/testdata/logic_test/privilege_builtins

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,3 +1380,178 @@ SELECT has_schema_privilege('testuser', 'owned_schema', 'create'),
13801380
has_schema_privilege('testuser2', 'owned_schema', 'usage')
13811381
----
13821382
true true false false
1383+
1384+
## has_system_privilege
1385+
subtest has_system_privilege
1386+
1387+
# Test as non-admin user (should not have system privileges).
1388+
user testuser
1389+
1390+
query BBBB
1391+
SELECT has_system_privilege('BACKUP'),
1392+
has_system_privilege('RESTORE'),
1393+
has_system_privilege('VIEWSYSTEMTABLE'),
1394+
has_system_privilege('MODIFYCLUSTERSETTING')
1395+
----
1396+
false false false false
1397+
1398+
query BBB
1399+
SELECT has_system_privilege('VIEWACTIVITY'),
1400+
has_system_privilege('VIEWCLUSTERMETADATA'),
1401+
has_system_privilege('CANCELQUERY')
1402+
----
1403+
false false false
1404+
1405+
# Test with specific user.
1406+
query BBB
1407+
SELECT has_system_privilege('testuser', 'BACKUP'),
1408+
has_system_privilege('testuser', 'RESTORE'),
1409+
has_system_privilege('testuser', 'VIEWSYSTEMTABLE')
1410+
----
1411+
false false false
1412+
1413+
# Test invalid privilege.
1414+
query error pgcode 22023 unrecognized privilege type: "INVALID"
1415+
SELECT has_system_privilege('INVALID')
1416+
1417+
# Test WITH GRANT OPTION.
1418+
query BB
1419+
SELECT has_system_privilege('BACKUP WITH GRANT OPTION'),
1420+
has_system_privilege('RESTORE WITH GRANT OPTION')
1421+
----
1422+
false false
1423+
1424+
# Test as admin user (should have all system privileges).
1425+
user root
1426+
1427+
query BBBB
1428+
SELECT has_system_privilege('BACKUP'),
1429+
has_system_privilege('RESTORE'),
1430+
has_system_privilege('VIEWSYSTEMTABLE'),
1431+
has_system_privilege('MODIFYCLUSTERSETTING')
1432+
----
1433+
true true true true
1434+
1435+
query BBB
1436+
SELECT has_system_privilege('VIEWACTIVITY'),
1437+
has_system_privilege('VIEWCLUSTERMETADATA'),
1438+
has_system_privilege('CANCELQUERY')
1439+
----
1440+
true true true
1441+
1442+
query BB
1443+
SELECT has_system_privilege('CREATEDB'),
1444+
has_system_privilege('CREATEROLE')
1445+
----
1446+
true true
1447+
1448+
# Test WITH GRANT OPTION as admin
1449+
query BB
1450+
SELECT has_system_privilege('BACKUP WITH GRANT OPTION'),
1451+
has_system_privilege('RESTORE WITH GRANT OPTION')
1452+
----
1453+
true true
1454+
1455+
# Test with specific user (admin checking testuser).
1456+
query BBB
1457+
SELECT has_system_privilege('testuser', 'BACKUP'),
1458+
has_system_privilege('testuser', 'RESTORE'),
1459+
has_system_privilege('testuser', 'VIEWSYSTEMTABLE')
1460+
----
1461+
false false false
1462+
1463+
# Test with specific user (admin checking root).
1464+
query BBB
1465+
SELECT has_system_privilege('root', 'BACKUP'),
1466+
has_system_privilege('root', 'RESTORE'),
1467+
has_system_privilege('root', 'VIEWSYSTEMTABLE')
1468+
----
1469+
true true true
1470+
1471+
# Grant some system privileges to testuser.
1472+
statement ok
1473+
GRANT SYSTEM VIEWACTIVITY TO testuser
1474+
1475+
statement ok
1476+
GRANT SYSTEM BACKUP TO testuser WITH GRANT OPTION
1477+
1478+
user testuser
1479+
1480+
# Test granted privileges
1481+
query BBBB
1482+
SELECT has_system_privilege('BACKUP'),
1483+
has_system_privilege('RESTORE'),
1484+
has_system_privilege('VIEWACTIVITY'),
1485+
has_system_privilege('VIEWSYSTEMTABLE')
1486+
----
1487+
true false true false
1488+
1489+
# Test case insensitivity.
1490+
query B
1491+
SELECT has_system_privilege('backup')
1492+
----
1493+
true
1494+
1495+
# Test multiple privileges (comma-separated).
1496+
query B
1497+
SELECT has_system_privilege('BACKUP, RESTORE')
1498+
----
1499+
true
1500+
1501+
user root
1502+
1503+
query BBB
1504+
SELECT has_system_privilege('testuser', 'BACKUP'),
1505+
has_system_privilege('testuser', 'RESTORE'),
1506+
has_system_privilege('testuser', 'VIEWSYSTEMTABLE')
1507+
----
1508+
true false false
1509+
1510+
# Test with OID.
1511+
query BBB
1512+
SELECT has_system_privilege((SELECT oid FROM pg_roles WHERE rolname = 'testuser'), 'BACKUP'),
1513+
has_system_privilege((SELECT oid FROM pg_roles WHERE rolname = 'testuser'), 'RESTORE'),
1514+
has_system_privilege((SELECT oid FROM pg_roles WHERE rolname = 'testuser'), 'VIEWSYSTEMTABLE')
1515+
----
1516+
true false false
1517+
1518+
# Test WITH GRANT OPTION for granted privilege (should be false unless granted with grant option).
1519+
query B
1520+
SELECT has_system_privilege('testuser', 'BACKUP WITH GRANT OPTION')
1521+
----
1522+
true
1523+
1524+
query B
1525+
SELECT has_system_privilege('testuser', 'VIEWACTIVITY WITH GRANT OPTION')
1526+
----
1527+
false
1528+
1529+
# Revoke privileges and check again.
1530+
statement ok
1531+
REVOKE SYSTEM VIEWACTIVITY FROM testuser
1532+
1533+
statement ok
1534+
REVOKE SYSTEM BACKUP FROM testuser
1535+
1536+
query B
1537+
SELECT has_system_privilege('testuser', 'BACKUP WITH GRANT OPTION')
1538+
----
1539+
false
1540+
1541+
query B
1542+
SELECT has_system_privilege('testuser', 'VIEWACTIVITY WITH GRANT OPTION')
1543+
----
1544+
false
1545+
1546+
# Test with non-existent user.
1547+
query error pgcode 42704 role 'non_existent_user' does not exist
1548+
SELECT has_system_privilege('non_existent_user', 'BACKUP')
1549+
1550+
# Test with non-existent user OID; note that the Postgres behavior is to return
1551+
# false for non-existent OIDs.
1552+
query B
1553+
SELECT has_system_privilege(99999::OID, 'BACKUP')
1554+
----
1555+
false
1556+
1557+
subtest end

pkg/sql/resolver.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
3232
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
3333
"github.com/cockroachdb/cockroach/pkg/sql/sqlerrors"
34+
"github.com/cockroachdb/cockroach/pkg/sql/syntheticprivilege"
3435
"github.com/cockroachdb/cockroach/pkg/util/metamorphic"
3536
"github.com/cockroachdb/errors"
3637
)
@@ -108,14 +109,14 @@ func (p *planner) HasAnyPrivilegeForSpecifier(
108109
user username.SQLUsername,
109110
privs []privilege.Privilege,
110111
) (eval.HasAnyPrivilegeResult, error) {
111-
desc, err := p.ResolveDescriptorForPrivilegeSpecifier(
112+
privObject, err := p.ResolveObjectForPrivilegeSpecifier(
112113
ctx,
113114
specifier,
114115
)
115116
if err != nil {
116117
return eval.HasNoPrivilege, err
117118
}
118-
if desc == nil {
119+
if privObject == nil {
119120
return eval.ObjectNotFound, nil
120121
}
121122

@@ -128,15 +129,19 @@ func (p *planner) HasAnyPrivilegeForSpecifier(
128129
continue
129130
}
130131

131-
if ok, err := p.HasPrivilege(ctx, desc, priv.Kind, user); err != nil {
132+
if ok, err := p.HasPrivilege(ctx, privObject, priv.Kind, user); err != nil {
132133
return eval.HasNoPrivilege, err
133134
} else if !ok {
134135
continue
135136
}
136137

137138
if priv.GrantOption {
139+
privDesc, err := p.getPrivilegeDescriptor(ctx, privObject)
140+
if err != nil {
141+
return eval.HasNoPrivilege, err
142+
}
138143
isGrantable, err := p.CheckGrantOptionsForUser(
139-
ctx, desc.GetPrivileges(), desc, []privilege.Kind{priv.Kind}, user,
144+
ctx, privDesc, privObject, []privilege.Kind{priv.Kind}, user,
140145
)
141146
if err != nil {
142147
return eval.HasNoPrivilege, err
@@ -151,11 +156,11 @@ func (p *planner) HasAnyPrivilegeForSpecifier(
151156
return eval.HasNoPrivilege, nil
152157
}
153158

154-
// ResolveDescriptorForPrivilegeSpecifier resolves a tree.HasPrivilegeSpecifier
155-
// and returns the descriptor for the given object.
156-
func (p *planner) ResolveDescriptorForPrivilegeSpecifier(
159+
// ResolveObjectForPrivilegeSpecifier resolves a tree.HasPrivilegeSpecifier
160+
// and returns the privilege object for the given specifier.
161+
func (p *planner) ResolveObjectForPrivilegeSpecifier(
157162
ctx context.Context, specifier eval.HasPrivilegeSpecifier,
158-
) (catalog.Descriptor, error) {
163+
) (privilege.Object, error) {
159164
if specifier.DatabaseName != nil {
160165
return p.Descriptors().ByNameWithLeased(p.txn).Get().Database(ctx, *specifier.DatabaseName)
161166
} else if specifier.DatabaseOID != nil {
@@ -226,6 +231,9 @@ func (p *planner) ResolveDescriptorForPrivilegeSpecifier(
226231
} else if specifier.FunctionOID != nil {
227232
fnID := funcdesc.UserDefinedFunctionOIDToID(*specifier.FunctionOID)
228233
return p.Descriptors().ByIDWithLeased(p.txn).WithoutNonPublic().Get().Function(ctx, fnID)
234+
} else if specifier.IsGlobalPrivilege {
235+
// Global privileges use a synthetic privilege object.
236+
return syntheticprivilege.GlobalPrivilegeObject, nil
229237
}
230238
return nil, errors.AssertionFailedf("invalid HasPrivilegeSpecifier")
231239
}

pkg/sql/sem/builtins/fixed_oids.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,6 +2667,9 @@ var builtinOidsArray = []string{
26672667
2704: `crdb_internal.show_create_all_triggers(database_name: string) -> string`,
26682668
2705: `crdb_internal.session_pending_jobs() -> tuple{int AS job_id, string AS job_type, string AS description, string AS user_name}`,
26692669
2706: `crdb_internal.can_view_job(owner: string) -> bool`,
2670+
2707: `has_system_privilege(privilege: string) -> bool`,
2671+
2708: `has_system_privilege(user: string, privilege: string) -> bool`,
2672+
2709: `has_system_privilege(user: oid, privilege: string) -> bool`,
26702673
}
26712674

26722675
var builtinOidsBySignature map[string]oid.Oid

pkg/sql/sem/builtins/pg_builtins.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ func makePGPrivilegeInquiryDef(
434434
user = username.MakeSQLUsernameFromPreNormalizedString(userS)
435435
if user.Undefined() {
436436
if _, ok := arg.(*tree.DOid); ok {
437-
// Postgres returns falseifn no matching user is
437+
// Postgres returns false if no matching user is
438438
// found when given an OID.
439439
return tree.DBoolFalse, nil
440440
}
@@ -1873,6 +1873,35 @@ var pgBuiltins = map[string]builtinDefinition{
18731873
},
18741874
),
18751875

1876+
"has_system_privilege": makePGPrivilegeInquiryDef(
1877+
"system",
1878+
paramTypeOpts{},
1879+
func(ctx context.Context, evalCtx *eval.Context, args tree.Datums, user username.SQLUsername) (eval.HasAnyPrivilegeResult, error) {
1880+
// Build the privilege map dynamically from GlobalPrivileges so that
1881+
// this function automatically picks up new privileges as they are added.
1882+
m := make(privMap)
1883+
for _, priv := range privilege.GlobalPrivileges {
1884+
// ALL is not a valid input for this function.
1885+
if priv == privilege.ALL {
1886+
continue
1887+
}
1888+
privName := strings.ToUpper(string(priv.DisplayName()))
1889+
m[privName] = privilege.Privilege{Kind: priv}
1890+
m[privName+" WITH GRANT OPTION"] = privilege.Privilege{Kind: priv, GrantOption: true}
1891+
}
1892+
1893+
privs, err := parsePrivilegeStr(args[0], m)
1894+
if err != nil {
1895+
return eval.HasNoPrivilege, err
1896+
}
1897+
1898+
specifier := eval.HasPrivilegeSpecifier{
1899+
IsGlobalPrivilege: true,
1900+
}
1901+
return evalCtx.Planner.HasAnyPrivilegeForSpecifier(ctx, specifier, user, privs)
1902+
},
1903+
),
1904+
18761905
"pg_has_role": makePGPrivilegeInquiryDef(
18771906
"role",
18781907
paramTypeOpts{{"role", strOrOidTypes}},

pkg/sql/sem/eval/deps.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ type HasPrivilegeSpecifier struct {
167167
// This needs to be a user-defined function OID. Builtin function OIDs won't
168168
// work since they're not descriptors based.
169169
FunctionOID *oid.Oid
170+
171+
// Global privilege
172+
// When true, this specifier is for checking global/system privileges.
173+
IsGlobalPrivilege bool
170174
}
171175

172176
// TypeResolver is an interface for resolving types and type OIDs.

0 commit comments

Comments
 (0)