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 "errors"
8 "os"
9 "reflect"
10 "strings"
11
12 "github.com/carlosframework/platform/internal/rootstate"
13)
14
15type HistoryRecovery struct {
16 Binding HistoryBinding `json:"binding"`
17 Attempt HistoryAttempt `json:"attempt"`
18 Settled bool `json:"settled"`
19 Failed bool `json:"failed"`
20 Recovering bool `json:"recovering"`
21 Verified bool `json:"verified"`
22}
23
24type historyRecoveryRecord struct {
25 Schema int `json:"schema"`
26 HistoryRecovery
27}
28
29func historyRecoveryKey(r HistoryRecovery) string {
30 sum := sha256.Sum256([]byte(r.Binding.Account + "\x00" + r.Binding.App + "\x00" + r.Binding.Host + "\x00" + r.Attempt.ID))
31 return hex.EncodeToString(sum[:]) + ".outcome"
32}
33
34func validHistoryRecovery(r HistoryRecovery) bool {
35 return r.Binding.valid() && r.Attempt.ID != "" && r.Attempt.Source != "" && r.Attempt.Version != "" && r.Attempt.SHA256 != "" && r.Attempt.Platform != "" && !r.Attempt.Started.IsZero() && !r.Attempt.PromotedAt.IsZero()
36}
37
38func (h *RootHistory) recoveryOutcomes() ([]HistoryRecovery, error) {
39 if h == nil || h.Store == nil {
40 return nil, os.ErrPermission
41 }
42 names, err := h.Store.Names()
43 if err != nil {
44 return nil, err
45 }
46 var out []HistoryRecovery
47 for _, name := range names {
48 if !strings.HasSuffix(name, ".outcome") {
49 continue
50 }
51 raw, err := h.Store.Read(name)
52 if err != nil {
53 return nil, err
54 }
55 var r historyRecoveryRecord
56 if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &r) != nil || r.Schema != 1 || !validHistoryRecovery(r.HistoryRecovery) || historyRecoveryKey(r.HistoryRecovery) != name {
57 return nil, os.ErrPermission
58 }
59 if err := h.Store.Write(name, raw); err != nil {
60 return nil, err
61 }
62 out = append(out, r.HistoryRecovery)
63 }
64 return out, nil
65}
66
67func (h *RootHistory) RetainRecovery(r HistoryRecovery) error {
68 if h == nil || h.Store == nil || !validHistoryRecovery(r) {
69 return os.ErrPermission
70 }
71 r.Attempt.Started, r.Attempt.PromotedAt = r.Attempt.Started.UTC(), r.Attempt.PromotedAt.UTC()
72 key := historyRecoveryKey(r)
73 raw, err := h.Store.Read(key)
74 if err == nil {
75 var old historyRecoveryRecord
76 if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &old) != nil || old.Schema != 1 || !validHistoryRecovery(old.HistoryRecovery) || old.Binding != r.Binding || !reflect.DeepEqual(old.Attempt, r.Attempt) {
77 return os.ErrPermission
78 }
79 r.Settled = r.Settled || old.Settled
80 r.Failed = r.Failed || old.Failed
81 r.Recovering = r.Recovering || old.Recovering
82 r.Verified = r.Verified || old.Verified
83 } else if !errors.Is(err, os.ErrNotExist) {
84 return err
85 }
86 raw, err = json.Marshal(historyRecoveryRecord{Schema: 1, HistoryRecovery: r})
87 if err != nil {
88 return err
89 }
90 return h.Store.Write(key, raw)
91}
92
93func (h *RootHistory) recoveryRefusals(outcomes []HistoryRecovery, b HistoryBinding) ([]Refusal, error) {
94 var refs []Refusal
95 count := 0
96 for _, r := range outcomes {
97 if r.Binding != b {
98 continue
99 }
100 count++
101 if count > historyLimit {
102 return nil, os.ErrPermission
103 }
104 retired, err := h.recoveryRetired(r, outcomes)
105 if err != nil {
106 return nil, err
107 }
108 if retired {
109 continue
110 }
111 if !r.Settled {
112 return nil, os.ErrPermission
113 }
114 if r.Failed || r.Recovering {
115 refs = append(refs, Refusal{Schema: 1, Host: b.Host, App: b.App, Version: r.Attempt.Version, SHA256: r.Attempt.SHA256, Platform: r.Attempt.Platform, PointerPromotedAt: r.Attempt.PromotedAt, RefusedAt: r.Attempt.Started, Kind: RefusalNotUp})
116 } else if !r.Verified {
117 return nil, os.ErrPermission
118 }
119 }
120 return refs, nil
121}
122