package regosql import ( "fmt" "golang.org/x/xerrors" "github.com/open-policy-agent/opa/ast" "github.com/coder/coder/v2/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": { // "": [""] // } // // 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) } }