summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/internal/namespace/aliasing.go
blob: adbaa9468ed8e7c831d8d4a90c505e7c77d3f5f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package namespace

import (
	"sort"

	"github.com/authzed/spicedb/pkg/schema"
)

// computePermissionAliases computes a map of aliases between the various permissions in a
// namespace. A permission is considered an alias if it *directly* refers to another permission
// or relation without any other form of expression.
func computePermissionAliases(typeDefinition *schema.ValidatedDefinition) (map[string]string, error) {
	aliases := map[string]string{}
	done := map[string]struct{}{}
	unresolvedAliases := map[string]string{}

	for _, rel := range typeDefinition.Namespace().Relation {
		// Ensure the relation has a rewrite...
		if rel.GetUsersetRewrite() == nil {
			done[rel.Name] = struct{}{}
			continue
		}

		// ... with a union ...
		union := rel.GetUsersetRewrite().GetUnion()
		if union == nil {
			done[rel.Name] = struct{}{}
			continue
		}

		// ... with a single child ...
		if len(union.Child) != 1 {
			done[rel.Name] = struct{}{}
			continue
		}

		// ... that is a computed userset.
		computedUserset := union.Child[0].GetComputedUserset()
		if computedUserset == nil {
			done[rel.Name] = struct{}{}
			continue
		}

		// If the aliased item is a relation, then we've found the alias target.
		aliasedPermOrRel := computedUserset.GetRelation()
		if !typeDefinition.IsPermission(aliasedPermOrRel) {
			done[rel.Name] = struct{}{}
			aliases[rel.Name] = aliasedPermOrRel
			continue
		}

		// Otherwise, add the permission to the working set.
		unresolvedAliases[rel.Name] = aliasedPermOrRel
	}

	for len(unresolvedAliases) > 0 {
		startingCount := len(unresolvedAliases)
		for relName, aliasedPermission := range unresolvedAliases {
			if _, ok := done[aliasedPermission]; ok {
				done[relName] = struct{}{}

				if alias, ok := aliases[aliasedPermission]; ok {
					aliases[relName] = alias
				} else {
					aliases[relName] = aliasedPermission
				}
				delete(unresolvedAliases, relName)
				continue
			}
		}
		if len(unresolvedAliases) == startingCount {
			keys := make([]string, 0, len(unresolvedAliases))
			for key := range unresolvedAliases {
				keys = append(keys, key)
			}
			sort.Strings(keys)
			return nil, NewPermissionsCycleErr(typeDefinition.Namespace().Name, keys)
		}
	}

	return aliases, nil
}