271 lines
5.1 KiB
Go
271 lines
5.1 KiB
Go
package acl
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
// ACL represents a single PostgreSQL `aclitem` entry.
|
|
type ACL struct {
|
|
Privileges Privileges
|
|
GrantOptions Privileges
|
|
Role string
|
|
GrantedBy string
|
|
}
|
|
|
|
// GetGrantOption returns true if the acl has the grant option set for the
|
|
// specified priviledge.
|
|
func (a ACL) GetGrantOption(priv Privileges) bool {
|
|
if a.GrantOptions&priv != 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetPriviledge returns true if the acl has the specified priviledge set.
|
|
func (a ACL) GetPrivilege(priv Privileges) bool {
|
|
if a.Privileges&priv != 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Parse parses a PostgreSQL aclitem string and returns an ACL
|
|
func Parse(aclStr string) (ACL, error) {
|
|
acl := ACL{}
|
|
idx := strings.IndexByte(aclStr, '=')
|
|
if idx == -1 {
|
|
return ACL{}, fmt.Errorf("invalid aclStr format: %+q", aclStr)
|
|
}
|
|
|
|
acl.Role = aclStr[:idx]
|
|
|
|
aclLen := len(aclStr)
|
|
var i int
|
|
withGrant := func() bool {
|
|
if i+1 >= aclLen {
|
|
return false
|
|
}
|
|
|
|
if aclStr[i+1] == '*' {
|
|
i++
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
SCAN:
|
|
for i = idx + 1; i < aclLen; i++ {
|
|
switch aclStr[i] {
|
|
case 'w':
|
|
acl.Privileges |= Update
|
|
if withGrant() {
|
|
acl.GrantOptions |= Update
|
|
}
|
|
case 'r':
|
|
acl.Privileges |= Select
|
|
if withGrant() {
|
|
acl.GrantOptions |= Select
|
|
}
|
|
case 'a':
|
|
acl.Privileges |= Insert
|
|
if withGrant() {
|
|
acl.GrantOptions |= Insert
|
|
}
|
|
case 'd':
|
|
acl.Privileges |= Delete
|
|
if withGrant() {
|
|
acl.GrantOptions |= Delete
|
|
}
|
|
case 'D':
|
|
acl.Privileges |= Truncate
|
|
if withGrant() {
|
|
acl.GrantOptions |= Truncate
|
|
}
|
|
case 'x':
|
|
acl.Privileges |= References
|
|
if withGrant() {
|
|
acl.GrantOptions |= References
|
|
}
|
|
case 't':
|
|
acl.Privileges |= Trigger
|
|
if withGrant() {
|
|
acl.GrantOptions |= Trigger
|
|
}
|
|
case 'X':
|
|
acl.Privileges |= Execute
|
|
if withGrant() {
|
|
acl.GrantOptions |= Execute
|
|
}
|
|
case 'U':
|
|
acl.Privileges |= Usage
|
|
if withGrant() {
|
|
acl.GrantOptions |= Usage
|
|
}
|
|
case 'C':
|
|
acl.Privileges |= Create
|
|
if withGrant() {
|
|
acl.GrantOptions |= Create
|
|
}
|
|
case 'T':
|
|
acl.Privileges |= Temporary
|
|
if withGrant() {
|
|
acl.GrantOptions |= Temporary
|
|
}
|
|
case 'c':
|
|
acl.Privileges |= Connect
|
|
if withGrant() {
|
|
acl.GrantOptions |= Connect
|
|
}
|
|
case '/':
|
|
if i+1 <= aclLen {
|
|
acl.GrantedBy = aclStr[i+1:]
|
|
}
|
|
break SCAN
|
|
default:
|
|
return ACL{}, fmt.Errorf("invalid byte %c in aclitem at %d: %+q", aclStr[i], i, aclStr)
|
|
}
|
|
}
|
|
|
|
return acl, nil
|
|
}
|
|
|
|
// String produces a PostgreSQL aclitem-compatible string
|
|
func (a ACL) String() string {
|
|
b := new(bytes.Buffer)
|
|
bitMaskStr := permString(a.Privileges, a.GrantOptions)
|
|
role := a.Role
|
|
grantedBy := a.GrantedBy
|
|
|
|
b.Grow(len(role) + len("=") + len(bitMaskStr) + len("/") + len(grantedBy))
|
|
|
|
fmt.Fprint(b, role, "=", bitMaskStr)
|
|
|
|
if grantedBy != "" {
|
|
fmt.Fprint(b, "/", grantedBy)
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// permString is a small helper function that emits the permission bitmask as a
|
|
// string.
|
|
func permString(perms, grantOptions Privileges) string {
|
|
b := new(bytes.Buffer)
|
|
b.Grow(int(numPrivileges) * 2)
|
|
|
|
// From postgresql/src/include/utils/acl.h:
|
|
//
|
|
// /* string holding all privilege code chars, in order by bitmask position */
|
|
// #define ACL_ALL_RIGHTS_STR "arwdDxtXUCTc"
|
|
if perms&Insert != 0 {
|
|
fmt.Fprint(b, "a")
|
|
if grantOptions&Insert != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Select != 0 {
|
|
fmt.Fprint(b, "r")
|
|
if grantOptions&Select != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Update != 0 {
|
|
fmt.Fprint(b, "w")
|
|
if grantOptions&Update != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Delete != 0 {
|
|
fmt.Fprint(b, "d")
|
|
if grantOptions&Delete != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Truncate != 0 {
|
|
fmt.Fprint(b, "D")
|
|
if grantOptions&Truncate != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&References != 0 {
|
|
fmt.Fprint(b, "x")
|
|
if grantOptions&References != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Trigger != 0 {
|
|
fmt.Fprint(b, "t")
|
|
if grantOptions&Trigger != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Execute != 0 {
|
|
fmt.Fprint(b, "X")
|
|
if grantOptions&Execute != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Usage != 0 {
|
|
fmt.Fprint(b, "U")
|
|
if grantOptions&Usage != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Create != 0 {
|
|
fmt.Fprint(b, "C")
|
|
if grantOptions&Create != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Temporary != 0 {
|
|
fmt.Fprint(b, "T")
|
|
if grantOptions&Temporary != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
if perms&Connect != 0 {
|
|
fmt.Fprint(b, "c")
|
|
if grantOptions&Connect != 0 {
|
|
fmt.Fprint(b, "*")
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// quoteRole is a small helper function that handles the quoting of a role name,
|
|
// or PUBLIC, if no role is specified.
|
|
func quoteRole(role string) string {
|
|
if role == "" {
|
|
return "PUBLIC"
|
|
}
|
|
|
|
return pq.QuoteIdentifier(role)
|
|
}
|
|
|
|
// validRights checks to make sure a given acl's permissions and grant options
|
|
// don't exceed the specified mask valid privileges.
|
|
func validRights(acl ACL, validPrivs Privileges) bool {
|
|
if (acl.Privileges|validPrivs) == validPrivs &&
|
|
(acl.GrantOptions|validPrivs) == validPrivs {
|
|
return true
|
|
}
|
|
return false
|
|
}
|