nestedpkg.gno
3.36 Kb · 107 lines
1// Package nestedpkg provides helpers for package-path based access control.
2// It is useful for upgrade patterns relying on namespaces.
3//
4// SECURITY: every exported helper takes `rlm realm` and reads both
5// `rlm.PkgPath()` and `rlm.Previous().PkgPath()` to make an
6// authorization decision. To close Class-2 designation forgery (a
7// hostile realm stashes a captured realm value and passes it back to
8// spoof identity), every helper gates on `rlm.IsCurrent()` first. The
9// Is* predicates return false on stale rlm (fail-closed); the Assert*
10// helpers panic. See docs/resources/gno-security.md.
11package nestedpkg
12
13import "strings"
14
15// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm.
16func IsCallerSubPath(_ int, rlm realm) bool {
17 if !rlm.IsCurrent() {
18 return false
19 }
20 var (
21 curPath = rlm.PkgPath() + "/"
22 prevPath = rlm.Previous().PkgPath() + "/"
23 )
24 return strings.HasPrefix(prevPath, curPath)
25}
26
27// AssertCallerIsSubPath panics if IsCallerSubPath returns false.
28func AssertCallerIsSubPath(_ int, rlm realm) {
29 if !rlm.IsCurrent() {
30 panic("unauthorized: rlm is not the caller's live cur")
31 }
32 var (
33 curPath = rlm.PkgPath() + "/"
34 prevPath = rlm.Previous().PkgPath() + "/"
35 )
36 if !strings.HasPrefix(prevPath, curPath) {
37 panic("call restricted to nested packages. current realm is " + curPath + ", previous realm is " + prevPath)
38 }
39}
40
41// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm.
42func IsCallerParentPath(_ int, rlm realm) bool {
43 if !rlm.IsCurrent() {
44 return false
45 }
46 var (
47 curPath = rlm.PkgPath() + "/"
48 prevPath = rlm.Previous().PkgPath() + "/"
49 )
50 return strings.HasPrefix(curPath, prevPath)
51}
52
53// AssertCallerIsParentPath panics if IsCallerParentPath returns false.
54func AssertCallerIsParentPath(_ int, rlm realm) {
55 if !rlm.IsCurrent() {
56 panic("unauthorized: rlm is not the caller's live cur")
57 }
58 var (
59 curPath = rlm.PkgPath() + "/"
60 prevPath = rlm.Previous().PkgPath() + "/"
61 )
62 if !strings.HasPrefix(curPath, prevPath) {
63 panic("call restricted to parent packages. current realm is " + curPath + ", previous realm is " + prevPath)
64 }
65}
66
67// IsSameNamespace checks if the caller realm and the current realm are in the same namespace.
68func IsSameNamespace(_ int, rlm realm) bool {
69 if !rlm.IsCurrent() {
70 return false
71 }
72 var (
73 curNs = nsFromPath(rlm.PkgPath()) + "/"
74 prevNs = nsFromPath(rlm.Previous().PkgPath()) + "/"
75 )
76 return curNs == prevNs
77}
78
79// AssertIsSameNamespace panics if IsSameNamespace returns false.
80func AssertIsSameNamespace(_ int, rlm realm) {
81 if !rlm.IsCurrent() {
82 panic("unauthorized: rlm is not the caller's live cur")
83 }
84 var (
85 curNs = nsFromPath(rlm.PkgPath()) + "/"
86 prevNs = nsFromPath(rlm.Previous().PkgPath()) + "/"
87 )
88 if curNs != prevNs {
89 panic("call restricted to packages from the same namespace. current realm is " + curNs + ", previous realm is " + prevNs)
90 }
91}
92
93// nsFromPath extracts the namespace from a package path.
94func nsFromPath(pkgpath string) string {
95 parts := strings.Split(pkgpath, "/")
96
97 // Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/...
98 // XXX: Consider extra checks.
99 // XXX: Support non gno.land domains, where p/ and r/ won't be enforced.
100 if len(parts) >= 3 {
101 return parts[2]
102 }
103 return ""
104}
105
106// XXX: Consider adding IsCallerDirectlySubPath
107// XXX: Consider adding IsCallerDirectlyParentPath