Migration This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs. Contact me

internal/access/access.go

79436f09f5539aac9b6f836b5b9049bbbece48aa · 2.7 KB · 82 lines raw

1 // Package access holds the rules that decide who may see or share a
2 // repository. It deliberately imports nothing from core: the visibility
3 // rule is used by the 404 gate, the index module, share-link minting, and
4 // share-link redemption, and putting it in one dependency-free place is
5 // what keeps those four from drifting apart.
6 package access
7
8 import (
9 "strings"
10 "time"
11 )
12
13 // Policy is the authorization-relevant subset of a repository's config.
14 // core.RepoInfo is the full config; Policy is what the rules below need.
15 type Policy struct {
16 Slug string
17 Hidden bool
18 VisibleTo []string
19 ShareRevokedAfter time.Time
20 }
21
22 // Identity is the verified caller. SharedSlugs/SharedIssuedAt come from a
23 // valid gsg_share cookie and are empty when the caller has no share grant.
24 type Identity struct {
25 User string
26 SharedSlugs []string
27 SharedIssuedAt time.Time
28 }
29
30 // CanView reports whether id may see the repository at all. Public repos
31 // are visible to everyone; a hidden repo is visible to an owner named in
32 // gsg.visible-to or to an unrevoked share grant.
33 func (p Policy) CanView(id Identity) bool {
34 if !p.Hidden {
35 return true
36 }
37 return p.canViewAsOwner(id.User) ||
38 (p.coversShare(id.SharedSlugs) && ShareLive(p.ShareRevokedAfter, id.SharedIssuedAt))
39 }
40
41 // CanShare reports whether user may mint a share link for this repository.
42 // Only hidden repos are shareable (sharing a public repo would grant
43 // nothing new), and only an owner may mint -- a share grant cannot be
44 // amplified into a broader grant.
45 func (p Policy) CanShare(user string) bool {
46 return p.Hidden && p.canViewAsOwner(user)
47 }
48
49 // canViewAsOwner matches gsg.visible-to case-insensitively. Anonymous
50 // callers never match: "" would otherwise vacuous-match a blank entry.
51 func (p Policy) canViewAsOwner(user string) bool {
52 if user == "" {
53 return false
54 }
55 for _, u := range p.VisibleTo {
56 if strings.EqualFold(u, user) {
57 return true
58 }
59 }
60 return false
61 }
62
63 func (p Policy) coversShare(slugs []string) bool {
64 for _, s := range slugs {
65 if s == p.Slug {
66 return true
67 }
68 }
69 return false
70 }
71
72 // ShareLive reports whether a grant issued at issuedAt survives a
73 // repository's gsg.share-revoked-after value. A zero revokedAfter means no
74 // revocation; a grant issued at or before the timestamp is revoked, which
75 // is why the comparison is strict -- the operator's intent is "everything
76 // issued before this moment is dead", and the two sides are second-granular.
77 //
78 // The free function is for callers (share redemption) that only have the
79 // timestamp, not a full Policy.
80 func ShareLive(revokedAfter, issuedAt time.Time) bool {
81 return revokedAfter.IsZero() || issuedAt.After(revokedAfter)
82 }