Skip to content

Commit 2532669

Browse files
committed
WIP grant.go
Former-commit-id: 3f95b91
1 parent 2ef4f95 commit 2532669

2 files changed

Lines changed: 205 additions & 52 deletions

File tree

grant.go

Lines changed: 194 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package main
22

33
import "sort"
4+
import "os"
45
import "fmt"
56
import "strings"
7+
import "regexp"
68
import "database/sql"
79
import "github.com/joncrlsn/pgutil"
8-
import "regexp"
910

1011
var aclRegex = regexp.MustCompile(`([a-zA-Z0-9]+)*=([rwadDxtXUCcT]+)/([a-zA-Z0-9]+)$`)
1112

@@ -43,6 +44,12 @@ func (slice GrantRows) Less(i, j int) bool {
4344
if slice[i]["column_name"] != slice[j]["column_name"] {
4445
return slice[i]["column_name"] < slice[j]["column_name"]
4546
}
47+
if slice[i]["relationship_acl"] != slice[j]["relationship_acl"] {
48+
return slice[i]["relationship_acl"] < slice[j]["relationship_acl"]
49+
}
50+
if slice[i]["column_acl"] != slice[j]["column_acl"] {
51+
return slice[i]["column_acl"] < slice[j]["column_acl"]
52+
}
4653
return false
4754
}
4855

@@ -92,7 +99,7 @@ func (c *GrantSchema) NextRow() bool {
9299
func (c *GrantSchema) Compare(obj interface{}) int {
93100
c2, ok := obj.(*GrantSchema)
94101
if !ok {
95-
fmt.Println("Error!!!, change needs a GrantSchema instance", c2)
102+
fmt.Println("Error!!!, Compare needs a GrantSchema instance", c2)
96103
return +999
97104
}
98105

@@ -114,49 +121,55 @@ func (c *GrantSchema) Compare(obj interface{}) int {
114121
func (c *GrantSchema) Add() {
115122
fmt.Println("--Add")
116123

117-
if c.get("column_acl") != "null" && len(c.get("column_acl")) > 0 {
118-
lines := strings.Split(c.get("column_acl"), "\n")
119-
for _, line := range lines {
120-
roleName, perms := parseGrants(line)
121-
fmt.Printf("GRANT %s (%s) ON %s TO %s; \n", strings.Join(perms, ", "), c.get("column_name"), c.get("relationship_name"), roleName)
122-
}
123-
} else if c.get("relationship_acl") != "null" && len(c.get("relationship_acl")) > 0 {
124-
lines := strings.Split(c.get("relationship_acl"), "\n")
125-
for _, line := range lines {
126-
roleName, perms := parseGrants(line)
127-
fmt.Printf("GRANT %s ON %s TO %s; \n", strings.Join(perms, ", "), c.get("relationship_name"), roleName)
128-
}
124+
acls := parseGrants(c.get("relationship_acl"))
125+
for _, acl := range acls {
126+
fmt.Printf("GRANT %s ON %s TO %s;\n", strings.Join(acl.grants, ", "), c.get("relationship_name"), acl.role)
127+
}
128+
129+
acls = parseGrants(c.get("column_acl"))
130+
for _, acl := range acls {
131+
fmt.Printf("GRANT %s (%s) ON %s TO %s;\n", strings.Join(acl.grants, ", "), c.get("column_name"), c.get("relationship_name"), acl.role)
129132
}
130133
}
131134

132135
// Drop prints SQL to drop the column
133136
func (c *GrantSchema) Drop() {
134137
fmt.Println("--Drop")
135138

136-
if c.get("column_acl") != "null" && len(c.get("column_acl")) > 0 {
137-
lines := strings.Split(c.get("column_acl"), "\n")
138-
for _, line := range lines {
139-
roleName, perms := parseGrants(line)
140-
fmt.Printf("REVOKE %s (%s) ON %s TO %s; \n", strings.Join(perms, ", "), c.get("column_name"), c.get("relationship_name"), roleName)
141-
}
142-
} else if c.get("relationship_acl") != "null" && len(c.get("relationship_acl")) > 0 {
143-
lines := strings.Split(c.get("relationship_acl"), "\n")
144-
for _, line := range lines {
145-
roleName, perms := parseGrants(line)
146-
fmt.Printf("REVOKE %s ON %s TO %s; \n", strings.Join(perms, ", "), c.get("relationship_name"), roleName)
147-
}
139+
acls := parseGrants(c.get("relationship_acl"))
140+
for _, acl := range acls {
141+
fmt.Printf("REVOKE %s ON %s TO %s;\n", strings.Join(acl.grants, ", "), c.get("relationship_name"), acl.role)
142+
}
143+
144+
acls = parseGrants(c.get("column_acl"))
145+
for _, acl := range acls {
146+
fmt.Printf("REVOKE %s (%s) ON %s TO %s;\n", strings.Join(acl.grants, ", "), c.get("column_name"), c.get("relationship_name"), acl.role)
148147
}
149148
}
150149

151-
// Change handles the case where the table and column match, but the details do not
150+
// Change handles the case where the relationship and column match, but the details do not
152151
func (c *GrantSchema) Change(obj interface{}) {
153152
c2, ok := obj.(*GrantSchema)
154153
if !ok {
155154
fmt.Println("-- Error!!!, change needs a GrantSchema instance", c2)
156155
}
156+
157+
{
158+
acls1 := parseGrants(c.get("relationship_acl"))
159+
acls2 := parseGrants(c2.get("relationship_acl"))
160+
_diffGrants(acls1, acls2, c.get("relationship_name"), c.get("column_name"))
161+
}
162+
163+
{
164+
// if c.get("column_acl") != "null" && len(c.get("column_acl")) > 0 {
165+
acls1 := parseGrants(c.get("column_acl"))
166+
acls2 := parseGrants(c2.get("column_acl"))
167+
_diffGrants(acls1, acls2, c.get("relationship_name"), c.get("column_name"))
168+
}
169+
157170
fmt.Println("--Change")
158-
fmt.Printf("rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c.get("relationship_name"), c.get("relationship_acl"), c.get("column_name"), c.get("column_acl"))
159-
fmt.Printf("rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c2.get("relationship_name"), c2.get("relationship_acl"), c2.get("column_name"), c2.get("column_acl"))
171+
fmt.Printf("--1 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c.get("relationship_name"), c.get("relationship_acl"), c.get("column_name"), c.get("column_acl"))
172+
fmt.Printf("--2 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c2.get("relationship_name"), c2.get("relationship_acl"), c2.get("column_name"), c2.get("column_acl"))
160173
}
161174

162175
// ==================================
@@ -167,25 +180,53 @@ func (c *GrantSchema) Change(obj interface{}) {
167180
* Compare the columns in the two databases
168181
*/
169182
func compareGrants(conn1 *sql.DB, conn2 *sql.DB) {
183+
fmt.Println(" Grant is broken right now. Please come back later! :-)")
184+
os.Exit(0)
185+
170186
sql := `
171187
SELECT
172188
n.nspname AS schema
173189
, CASE c.relkind
174-
WHEN 'r' THEN 'TABLE'
175-
WHEN 'v' THEN 'VIEW'
176-
WHEN 'S' THEN 'SEQUENCE'
177-
WHEN 'f' THEN 'FOREIGN TABLE'
178-
END as type
190+
WHEN 'r' THEN 'TABLE'
191+
WHEN 'v' THEN 'VIEW'
192+
WHEN 'S' THEN 'SEQUENCE'
193+
WHEN 'f' THEN 'FOREIGN TABLE'
194+
END as type
179195
, c.relname AS relationship_name
180-
, pg_catalog.array_to_string(c.relacl, E'\n') AS relationship_acl
196+
-- , pg_catalog.array_to_string(c.relacl, E'\n') AS relationship_acl
197+
-- , pg_catalog.array_to_string(a.attacl, E'\n') AS column_acl
198+
, unnest(c.relacl) AS relationship_acl
181199
, a.attname AS column_name
182-
, pg_catalog.array_to_string(a.attacl, E'\n') AS column_acl
200+
, a.attacl AS column_acl
183201
FROM pg_catalog.pg_class c
184202
LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
185-
LEFT JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND NOT a.attisdropped AND a.attacl IS NOT NULL)
203+
LEFT JOIN (SELECT attname, unnest(attacl) AS attacl, attrelid, attisdropped
204+
FROM pg_catalog.pg_attribute
205+
WHERE NOT attisdropped AND attacl IS NOT NULL)
206+
AS a ON (a.attrelid = c.oid)
186207
WHERE c.relkind IN ('r', 'v', 'S', 'f')
187-
AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid);
208+
AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)
209+
ORDER BY n.nspname, c.relname, a.attname;
188210
`
211+
//oldSql := `SELECT
212+
// n.nspname AS schema
213+
// , CASE c.relkind
214+
// WHEN 'r' THEN 'TABLE'
215+
// WHEN 'v' THEN 'VIEW'
216+
// WHEN 'S' THEN 'SEQUENCE'
217+
// WHEN 'f' THEN 'FOREIGN TABLE'
218+
// END as type
219+
// , c.relname AS relationship_name
220+
// , pg_catalog.array_to_string(c.relacl, E'\n') AS relationship_acl
221+
// , a.attname AS column_name
222+
// , pg_catalog.array_to_string(a.attacl, E'\n') AS column_acl
223+
//FROM pg_catalog.pg_class c
224+
//LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
225+
//LEFT JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND NOT a.attisdropped AND a.attacl IS NOT NULL)
226+
//WHERE c.relkind IN ('r', 'v', 'S', 'f')
227+
// AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid);
228+
//`
229+
189230
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
190231
rowChan2, _ := pgutil.QueryStrings(conn2, sql)
191232

@@ -208,10 +249,120 @@ WHERE c.relkind IN ('r', 'v', 'S', 'f')
208249
doDiff(schema1, schema2)
209250
}
210251

252+
// ==================================
253+
// Private functions and structures
254+
// ==================================
255+
256+
func _diffGrants(acls1 RoleAcls, acls2 RoleAcls, table string, column string) {
257+
//fmt.Printf("GRANT %s (%s) ON %s TO %s; \n", strings.Join(perms, ", "), c.get("column_name"), c.get("relationship_name"), roleName)
258+
ix1, ix2 := 0, 0
259+
more1 := ix1 < len(acls1)
260+
more2 := ix2 < len(acls2)
261+
var acl1 RoleAcl
262+
var acl2 RoleAcl
263+
if more1 {
264+
acl1 = acls1[ix1]
265+
}
266+
if more2 {
267+
acl2 = acls2[ix2]
268+
}
269+
for more1 || more2 {
270+
if acl1.role == acl2.role {
271+
//_diffRole(acl1.grants, acl2.grants, acl1.role, table, column)
272+
ix1 += 1
273+
ix2 += 1
274+
more1 := ix1 < len(acls1)
275+
more2 := ix2 < len(acls2)
276+
if more1 {
277+
acl1 = acls1[ix1]
278+
}
279+
if more2 {
280+
acl2 = acls2[ix2]
281+
}
282+
} else if acl1.role < acl2.role {
283+
} else if acl1.role > acl2.role {
284+
}
285+
// compareVal := db1.Compare(db2)
286+
// if compareVal == 0 {
287+
// // table and column match, look for non-identifying changes
288+
// db1.Change(db2)
289+
// more1 = db1.NextRow()
290+
// more2 = db2.NextRow()
291+
// } else if compareVal < 0 {
292+
// // db2 is missing a value that db1 has
293+
// if more1 {
294+
// db1.Add()
295+
// more1 = db1.NextRow()
296+
// } else {
297+
// // db1 is at the end
298+
// db2.Drop()
299+
// more2 = db2.NextRow()
300+
// }
301+
// } else if compareVal > 0 {
302+
// // db2 has an extra column that we don't want
303+
// if more2 {
304+
// db2.Drop()
305+
// more2 = db2.NextRow()
306+
// } else {
307+
// // db2 is at the end
308+
// db1.Add()
309+
// more1 = db1.NextRow()
310+
// }
311+
// }
312+
}
313+
}
314+
315+
//
316+
// RoleAcls (a sortable slice of RoleAcl instances)
317+
//
318+
type RoleAcls []RoleAcl
319+
320+
func (slice RoleAcls) Len() int {
321+
return len(slice)
322+
}
323+
324+
func (slice RoleAcls) Less(i, j int) bool {
325+
return slice[i].role < slice[j].role
326+
}
327+
328+
func (slice RoleAcls) Swap(i, j int) {
329+
slice[i], slice[j] = slice[j], slice[i]
330+
}
331+
332+
func (slice RoleAcls) get(role string) RoleAcl {
333+
for _, roleAcl := range slice {
334+
if roleAcl.role == role {
335+
return roleAcl
336+
}
337+
}
338+
return RoleAcl{role: "", grants: []string{}}
339+
}
340+
341+
//
342+
// RoleAcl
343+
//
344+
type RoleAcl struct {
345+
role string
346+
grants []string
347+
}
348+
349+
// parseGrants breaks up a set of ACL lines and parses them into a slice of permission strings per line.
350+
func parseGrants(acl string) (roleAcls RoleAcls) {
351+
lines := strings.Split(acl, "\n")
352+
roleAcls = make(RoleAcls, 0)
353+
for _, line := range lines {
354+
roleName, perms := _parseGrants(line)
355+
roleAcl := RoleAcl{role: roleName, grants: perms}
356+
roleAcls = append(roleAcls, roleAcl)
357+
}
358+
sort.Sort(roleAcls)
359+
return
360+
}
361+
211362
/*
212-
parseGrants converts an ACL string into a slice of permission strings
363+
_parseGrants converts an ACL line into a slice of permission strings
213364
214-
Example of an ACL: c42ro=rwa/c42 (we want to split out the "rwa" part)
365+
Example of an ACL: c42ro=rwa/c42 (we want to separate out the "rwa" part)
215366
216367
rolename=xxxx -- privileges granted to a role
217368
=xxxx -- privileges granted to PUBLIC
@@ -231,17 +382,17 @@ rolename=xxxx -- privileges granted to a role
231382
* -- grant option for preceding privilege
232383
/yyyy -- role that granted this privilege
233384
*/
234-
func parseGrants(acl string) (string, []string) {
235-
385+
func _parseGrants(acl string) (string, sort.StringSlice) {
236386
matches := aclRegex.FindStringSubmatch(acl)
237387
role := matches[1]
238388
perms := matches[2]
239-
var permWords []string
389+
permWords := make(sort.StringSlice, 0)
240390
for _, c := range strings.Split(perms, "") {
241391
permWord := permMap[c]
242392
if len(permWord) > 0 {
243393
permWords = append(permWords, permWord)
244394
}
245395
}
396+
permWords.Sort()
246397
return role, permWords
247398
}

grant_test.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import (
66
)
77

88
func Test_parseGrants(t *testing.T) {
9-
doParseGrants(t, "c42ro=rwa/c42", "c42ro", 3)
10-
doParseGrants(t, "=arwdDxt/c42", "", 7)
11-
doParseGrants(t, "user2=arwxt/postgres", "user2", 5)
9+
doParseGrants(t, "c42ro=rwa/c42", "c42ro", 3, 0)
10+
doParseGrants(t, "=arwdDxt/c42\nc42=rwad/postgres", "", 7, 0) // first of two lines
11+
doParseGrants(t, "=arwdDxt/c42\nc42=rwad/postgres", "c42", 4, 1) // second of two lines
12+
doParseGrants(t, "user2=arwxt/postgres", "user2", 5, 0)
1213
}
1314

1415
/*
@@ -46,13 +47,14 @@ func Test_diffGrants(t *testing.T) {
4647
doDiff(schema1, schema2)
4748
}
4849

49-
func doParseGrants(t *testing.T, acl string, expectedRole string, expectedPermCount int) {
50+
func doParseGrants(t *testing.T, acl string, expectedRole string, expectedPermCount int, index int) {
5051
fmt.Println("Testing", acl)
51-
role, perms := parseGrants(acl)
52-
if role != expectedRole {
53-
t.Error("Wrong role parsed: %s instead of %s", role, expectedRole)
52+
roleAcls := parseGrants(acl)
53+
roleAcl := roleAcls[index]
54+
if roleAcl.role != expectedRole {
55+
t.Error("Wrong role parsed: %s instead of %s", roleAcl.role, expectedRole)
5456
}
55-
if len(perms) != expectedPermCount {
56-
t.Error("Incorrect number of permissions parsed: %d instead of %d", len(perms), expectedPermCount)
57+
if len(roleAcl.grants) != expectedPermCount {
58+
t.Error("Incorrect number of permissions parsed: %d instead of %d", len(roleAcl.grants), expectedPermCount)
5759
}
5860
}

0 commit comments

Comments
 (0)