Monorepo for Tangled
tangled.org
1package knotacl
2
3import (
4 "context"
5 "fmt"
6 "slices"
7
8 "tangled.org/core/appview/db"
9 "tangled.org/core/appview/models"
10 "tangled.org/core/appview/pages"
11 "tangled.org/core/orm"
12 "tangled.org/core/rbac"
13)
14
15type reader interface {
16 repoPerms(ctx context.Context, repo *models.Repo, userDid string) ([]string, error)
17 knotMembers(ctx context.Context, host string) []string
18 collaborators(ctx context.Context, repo *models.Repo) []pages.Collaborator
19 isRepoCreateAllowed(ctx context.Context, host, userDid string) bool
20 isKnotMember(ctx context.Context, host, userDid string) bool
21}
22
23type legacyReader struct {
24 enforcer *rbac.Enforcer
25}
26
27func (r *legacyReader) repoPerms(ctx context.Context, repo *models.Repo, userDid string) ([]string, error) {
28 return r.enforcer.GetPermissionsInRepo(userDid, repo.Knot, repo.RepoIdentifier()), nil
29}
30
31func (r *legacyReader) knotMembers(ctx context.Context, host string) []string {
32 members, err := r.enforcer.GetUserByRole("server:member", host)
33 if err != nil {
34 return nil
35 }
36 return members
37}
38
39func (r *legacyReader) collaborators(ctx context.Context, repo *models.Repo) []pages.Collaborator {
40 policies, err := r.enforcer.E.GetImplicitUsersForResourceByDomain(repo.RepoIdentifier(), repo.Knot)
41 if err != nil {
42 return nil
43 }
44 return filterMap(policies, func(p []string) (pages.Collaborator, bool) {
45 // currently only two roles: owner and member
46 switch p[3] {
47 case "repo:owner":
48 return pages.Collaborator{Did: p[0], Role: "owner"}, true
49 case "repo:collaborator":
50 return pages.Collaborator{Did: p[0], Role: "collaborator"}, true
51 default:
52 return pages.Collaborator{}, false
53 }
54 })
55}
56
57func (r *legacyReader) isRepoCreateAllowed(ctx context.Context, host, userDid string) bool {
58 ok, err := r.enforcer.IsRepoCreateAllowed(userDid, host)
59 return err == nil && ok
60}
61
62func (r *legacyReader) isKnotMember(ctx context.Context, host, userDid string) bool {
63 knots, err := r.enforcer.GetKnotsForUser(userDid)
64 return err == nil && slices.Contains(knots, host)
65}
66
67type nativeReader struct {
68 client *roster
69 execer db.Execer
70}
71
72func (r *nativeReader) repoPerms(ctx context.Context, repo *models.Repo, userDid string) ([]string, error) {
73 if userDid == repo.Did {
74 return ownerPermissions(), nil
75 }
76 var perms []string
77 if r.isRegisteredOwner(ctx, repo.Knot, userDid) {
78 perms = serverOwnerRepoPermissions()
79 }
80 collabs, err := r.client.GetRepoCollaborators(ctx, repo.Knot, repo.RepoDid)
81 if err != nil {
82 return dedup(perms), fmt.Errorf("%w: %v", ErrKnotUnreachable, err)
83 }
84 if slices.Contains(collabs, userDid) {
85 perms = append(perms, collaboratorPermissions()...)
86 }
87 return dedup(perms), nil
88}
89
90func (r *nativeReader) knotMembers(ctx context.Context, host string) []string {
91 members, err := r.client.GetKnotMembers(ctx, host)
92 if err != nil {
93 return dedup(r.registeredOwners(ctx, host))
94 }
95 return dedup(append(members, r.registeredOwners(ctx, host)...))
96}
97
98func (r *nativeReader) collaborators(ctx context.Context, repo *models.Repo) []pages.Collaborator {
99 owner := pages.Collaborator{Did: repo.Did, Role: "owner"}
100 collabs, err := r.client.GetRepoCollaborators(ctx, repo.Knot, repo.RepoDid)
101 if err != nil {
102 return []pages.Collaborator{owner}
103 }
104 rows := filterMap(collabs, func(d string) (pages.Collaborator, bool) {
105 if d == repo.Did {
106 return pages.Collaborator{}, false
107 }
108 return pages.Collaborator{Did: d, Role: "collaborator"}, true
109 })
110 return append([]pages.Collaborator{owner}, rows...)
111}
112
113func (r *nativeReader) isRepoCreateAllowed(ctx context.Context, host, userDid string) bool {
114 members, err := r.client.GetKnotMembers(ctx, host)
115 if err == nil && slices.Contains(members, userDid) {
116 return true
117 }
118 return r.isRegisteredOwner(ctx, host, userDid)
119}
120
121func (r *nativeReader) isKnotMember(ctx context.Context, host, userDid string) bool {
122 return slices.Contains(r.knotMembers(ctx, host), userDid)
123}
124
125func (r *nativeReader) registeredOwners(ctx context.Context, host string) []string {
126 key := "r\x00" + host
127 if memo := memoFrom(ctx); memo != nil {
128 if v, ok := memo.get(key); ok {
129 return slices.Clone(v)
130 }
131 }
132 regs, err := db.GetRegistrations(r.execer, orm.FilterEq("domain", host))
133 if err != nil {
134 return nil
135 }
136 owners := filterMap(regs, func(reg models.Registration) (string, bool) {
137 return reg.ByDid, reg.Registered != nil
138 })
139 if memo := memoFrom(ctx); memo != nil {
140 memo.put(key, slices.Clone(owners))
141 }
142 return owners
143}
144
145func (r *nativeReader) isRegisteredOwner(ctx context.Context, host, userDid string) bool {
146 return slices.Contains(r.registeredOwners(ctx, host), userDid)
147}