coder/coderd/rbac/regosql/acl_group_var.go

105 lines
2.8 KiB
Go

package regosql
import (
"fmt"
"golang.org/x/xerrors"
"github.com/open-policy-agent/opa/ast"
"github.com/coder/coder/coderd/rbac/regosql/sqltypes"
)
var (
_ sqltypes.VariableMatcher = ACLGroupVar{}
_ sqltypes.Node = ACLGroupVar{}
)
// ACLGroupVar is a variable matcher that handles group_acl and user_acl.
// The sql type is a jsonb object with the following structure:
//
// "group_acl": {
// "<group_name>": ["<actions>"]
// }
//
// This is a custom variable matcher as json objects have arbitrary complexity.
type ACLGroupVar struct {
StructSQL string
// input.object.group_acl -> ["input", "object", "group_acl"]
StructPath []string
// FieldReference handles referencing the subfields, which could be
// more variables. We pass one in as the global one might not be correctly
// scoped.
FieldReference sqltypes.VariableMatcher
// Instance fields
Source sqltypes.RegoSource
GroupNode sqltypes.Node
}
func ACLGroupMatcher(fieldReference sqltypes.VariableMatcher, structSQL string, structPath []string) ACLGroupVar {
return ACLGroupVar{StructSQL: structSQL, StructPath: structPath, FieldReference: fieldReference}
}
func (ACLGroupVar) UseAs() sqltypes.Node { return ACLGroupVar{} }
func (g ACLGroupVar) ConvertVariable(rego ast.Ref) (sqltypes.Node, bool) {
// "left" will be a map of group names to actions in rego.
// {
// "all_users": ["read"]
// }
left, err := sqltypes.RegoVarPath(g.StructPath, rego)
if err != nil {
return nil, false
}
aclGrp := ACLGroupVar{
StructSQL: g.StructSQL,
StructPath: g.StructPath,
FieldReference: g.FieldReference,
Source: sqltypes.RegoSource(rego.String()),
}
// We expect 1 more term. Either a ref or a string.
if len(left) != 1 {
return nil, false
}
// If the remaining is a variable, then we need to convert it.
// Assuming we support variable fields.
ref, ok := left[0].Value.(ast.Ref)
if ok && g.FieldReference != nil {
groupNode, ok := g.FieldReference.ConvertVariable(ref)
if ok {
aclGrp.GroupNode = groupNode
return aclGrp, true
}
}
// If it is a string, we assume it is a literal
groupName, ok := left[0].Value.(ast.String)
if ok {
aclGrp.GroupNode = sqltypes.String(string(groupName))
return aclGrp, true
}
// If we have not matched it yet, then it is something we do not recognize.
return nil, false
}
func (g ACLGroupVar) SQLString(cfg *sqltypes.SQLGenerator) string {
return fmt.Sprintf("%s->%s", g.StructSQL, g.GroupNode.SQLString(cfg))
}
func (g ACLGroupVar) ContainsSQL(cfg *sqltypes.SQLGenerator, other sqltypes.Node) (string, error) {
switch other.UseAs().(type) {
// Only supports containing other strings.
case sqltypes.AstString:
return fmt.Sprintf("%s ? %s", g.SQLString(cfg), other.SQLString(cfg)), nil
default:
return "", xerrors.Errorf("unsupported acl group contains %T", other)
}
}