Migration
This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs.
Contact me
internal/access/access.go
d658fcc4ea9f3bb562fd00dce8f863934560c56c
· 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 | } |