Skip to content

Commit

Permalink
Merge pull request #2 from Leafly-com/feat-add-column-level-management
Browse files Browse the repository at this point in the history
fix: Use pg catalog tables for column grant info
  • Loading branch information
kda-jt authored Oct 27, 2022
2 parents 56974cd + 8d37b78 commit fd9991f
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 51 deletions.
8 changes: 8 additions & 0 deletions postgresql/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,14 @@ func setToPgIdentList(schema string, idents *schema.Set) string {
return strings.Join(quotedIdents, ",")
}

func setToPgIdentListWithoutSchema(idents *schema.Set) string {
quotedIdents := make([]string, idents.Len())
for i, ident := range idents.List() {
quotedIdents[i] = pq.QuoteIdentifier(ident.(string))
}
return strings.Join(quotedIdents, ",")
}

func setToPgIdentSimpleList(idents *schema.Set) string {
quotedIdents := make([]string, idents.Len())
for i, ident := range idents.List() {
Expand Down
69 changes: 20 additions & 49 deletions postgresql/resource_postgresql_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func resourcePostgreSQLGrant() *schema.Resource {
Create: PGResourceFunc(resourcePostgreSQLGrantCreate),
// Since all of this resource's arguments force a recreation
// there's no need for an Update function
//Update:
// Update:
Read: PGResourceFunc(resourcePostgreSQLGrantRead),
Delete: PGResourceFunc(resourcePostgreSQLGrantDelete),

Expand Down Expand Up @@ -325,52 +325,27 @@ func readColumnRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
missingColumns := d.Get("columns").(*schema.Set) // Getting columns from state.
// If the query returns a column, it is a removed from the missingColumns.

roleOID, err := getRoleOID(txn, d.Get("role").(string))
if err != nil {
return err
}

var rows *sql.Rows

// The following query is made up of 3 parts
// The first one simply aggregates all privileges on one column in one table into one line.
// The second part fetches all permissions on all columns for a given user & a given table in a give schema.
// The third part fetches all table-level permissions for the aforementioned table.
// Subtracting the third part from the second part allows us
// to get column-level privileges without those created by table-level privileges.
// The attacl column of pg_attribute contains information only about explicit column grants
query := `
SELECT table_name, column_name, array_agg(privilege_type) AS column_privileges
FROM (
SELECT table_name, column_name, privilege_type
FROM information_schema.column_privileges
WHERE
grantee = $1
AND
table_schema = $2
AND
table_name = $3
AND
privilege_type = $6
EXCEPT
SELECT pg_class.relname, pg_attribute.attname, privilege_type AS table_grant
FROM pg_class
JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
LEFT JOIN (
SELECT acls.*
FROM
(SELECT relname, relnamespace, relkind, (aclexplode(relacl)).* FROM pg_class c) as acls
WHERE grantee=$4
) privs
USING (relname, relnamespace, relkind)
LEFT JOIN pg_attribute ON pg_class.oid = pg_attribute.attrelid
WHERE nspname = $2 AND relkind = $5
)
AS col_privs_without_table_privs
GROUP BY col_privs_without_table_privs.table_name, col_privs_without_table_privs.column_name, col_privs_without_table_privs.privilege_type
ORDER BY col_privs_without_table_privs.column_name
SELECT relname AS table_name, attname AS column_name, array_agg(privilege_type) AS column_privileges
FROM (SELECT relname, attname, (aclexplode(attacl)).*
FROM pg_class
JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
JOIN pg_attribute ON pg_class.oid = attrelid
WHERE nspname = $2
AND relname = $3
AND relkind = $4)
AS col_privs
JOIN pg_roles ON pg_roles.oid = col_privs.grantee
WHERE rolname = $1
AND privilege_type = $5
GROUP BY col_privs.relname, col_privs.attname, col_privs.privilege_type
ORDER BY col_privs.attname
;`
rows, err = txn.Query(
query, d.Get("role").(string), d.Get("schema"), objects.List()[0], roleOID, objectTypes["table"], d.Get("privileges").(*schema.Set).List()[0],
rows, err := txn.Query(
query, d.Get("role").(string), d.Get("schema"), objects.List()[0], objectTypes["table"], d.Get("privileges").(*schema.Set).List()[0],
)

if err != nil {
Expand Down Expand Up @@ -565,14 +540,10 @@ func createGrantQuery(d *schema.ResourceData, privileges []string) string {
)
case "COLUMN":
objects := d.Get("objects").(*schema.Set)
columns := []string{}
for _, col := range d.Get("columns").(*schema.Set).List() {
columns = append(columns, col.(string))
}
query = fmt.Sprintf(
"GRANT %s (%s) ON TABLE %s TO %s",
strings.Join(privileges, ","),
strings.Join(columns, ","),
setToPgIdentListWithoutSchema(d.Get("columns").(*schema.Set)),
setToPgIdentList(d.Get("schema").(string), objects),
pq.QuoteIdentifier(d.Get("role").(string)),
)
Expand Down Expand Up @@ -645,7 +616,7 @@ func createRevokeQuery(d *schema.ResourceData) string {
query = fmt.Sprintf(
"REVOKE %s (%s) ON TABLE %s FROM %s",
setToPgIdentSimpleList(privileges),
setToPgIdentSimpleList(columns),
setToPgIdentListWithoutSchema(columns),
setToPgIdentList(d.Get("schema").(string), objects),
pq.QuoteIdentifier(d.Get("role").(string)),
)
Expand Down
4 changes: 2 additions & 2 deletions postgresql/resource_postgresql_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func TestCreateGrantQuery(t *testing.T) {
"role": roleName,
}),
privileges: []string{"SELECT"},
expected: fmt.Sprintf(`GRANT SELECT (col2,col1) ON TABLE %[1]s."o1" TO %s`, pq.QuoteIdentifier(databaseName), pq.QuoteIdentifier(roleName)),
expected: fmt.Sprintf(`GRANT SELECT (%[2]s,%[3]s) ON TABLE %[1]s."o1" TO %[4]s`, pq.QuoteIdentifier(databaseName), pq.QuoteIdentifier("col2"), pq.QuoteIdentifier("col1"), pq.QuoteIdentifier(roleName)),
},
{
resource: schema.TestResourceDataRaw(t, resourcePostgreSQLGrant().Schema, map[string]interface{}{
Expand Down Expand Up @@ -270,7 +270,7 @@ func TestCreateRevokeQuery(t *testing.T) {
"role": roleName,
"privileges": []interface{}{"SELECT"},
}),
expected: fmt.Sprintf(`REVOKE SELECT (col2,col1) ON TABLE %[1]s."o1" FROM %s`, pq.QuoteIdentifier(databaseName), pq.QuoteIdentifier(roleName)),
expected: fmt.Sprintf(`REVOKE SELECT ("col2","col1") ON TABLE %[1]s."o1" FROM %s`, pq.QuoteIdentifier(databaseName), pq.QuoteIdentifier(roleName)),
},
{
resource: schema.TestResourceDataRaw(t, resourcePostgreSQLGrant().Schema, map[string]interface{}{
Expand Down

0 comments on commit fd9991f

Please sign in to comment.