carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "os"
8 "slices"
9 "strings"
10
11 "github.com/carlosframework/platform/internal/rootstate"
12)
13
14type historyApp struct {
15 Schema int `json:"schema"`
16 Account string `json:"account"`
17 App string `json:"app"`
18 Revision uint64 `json:"revision"`
19 Complete bool `json:"complete"`
20 Hosts []string `json:"hosts"`
21}
22
23func historyAppKey(acct, app string) string {
24 sum := sha256.Sum256([]byte(acct + "\x00" + app))
25 return hex.EncodeToString(sum[:]) + ".app"
26}
27
28func validHistoryApp(a historyApp) bool {
29 if a.Schema != 1 || !ValidAccount(a.Account) || !ValidApp(a.App) || a.Revision == 0 || len(a.Hosts) > historyLimit {
30 return false
31 }
32 seen := map[string]bool{}
33 for _, host := range a.Hosts {
34 b := HistoryBinding{Host: host, Account: a.Account, App: a.App}
35 if !b.valid() || seen[host] {
36 return false
37 }
38 seen[host] = true
39 }
40 return true
41}
42
43func (h *RootHistory) writeApp(a historyApp) error {
44 if h == nil || h.Store == nil || !validHistoryApp(a) {
45 return os.ErrPermission
46 }
47 raw, err := json.Marshal(a)
48 if err != nil {
49 return err
50 }
51 return h.Store.Write(historyAppKey(a.Account, a.App), raw)
52}
53
54func (h *RootHistory) AppRefusals(acct, app string) ([]Refusal, error) {
55 if err := h.reconstructionReady(); err != nil {
56 return nil, err
57 }
58 if h == nil || h.Store == nil || !ValidAccount(acct) || !ValidApp(app) {
59 return nil, os.ErrPermission
60 }
61 key := historyAppKey(acct, app)
62 raw, err := h.Store.Read(key)
63 if err != nil {
64 return nil, err
65 }
66 var a historyApp
67 if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &a) != nil || !validHistoryApp(a) || !a.Complete || a.Account != acct || a.App != app {
68 return nil, os.ErrPermission
69 }
70 if err := h.Store.Write(key, raw); err != nil {
71 return nil, err
72 }
73 names, err := h.Store.Names()
74 if err != nil {
75 return nil, err
76 }
77 for _, name := range names {
78 if name == rootBakeKey || !strings.HasSuffix(name, ".json") {
79 continue
80 }
81 raw, err := h.Store.Read(name)
82 var r historyRecord
83 if err != nil {
84 return nil, err
85 }
86 if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &r) != nil || !validHistory(r, r.Binding) || historyKey(r.Binding) != name {
87 return nil, os.ErrPermission
88 }
89 if r.Binding.Account == acct && r.Binding.App == app && !slices.Contains(a.Hosts, r.Binding.Host) {
90 return nil, os.ErrPermission
91 }
92 }
93 outcomes, err := h.recoveryOutcomes()
94 if err != nil {
95 return nil, err
96 }
97 for _, r := range outcomes {
98 if r.Binding.Account == acct && r.Binding.App == app && !slices.Contains(a.Hosts, r.Binding.Host) {
99 return nil, os.ErrPermission
100 }
101 }
102 var out []Refusal
103 for _, host := range a.Hosts {
104 refs, err := h.Refusals(HistoryBinding{Host: host, Account: acct, App: app})
105 if err != nil {
106 return nil, err
107 }
108 out = append(out, refs...)
109 }
110 return out, nil
111}
112
113func (h *RootHistory) MintRefusals(b HistoryBinding) ([]Refusal, error) {
114 if _, err := h.Refusals(b); err != nil {
115 return nil, err
116 }
117 return h.AppRefusals(b.Account, b.App)
118}
119