package release import ( "crypto/sha256" "encoding/hex" "encoding/json" "os" "slices" "strings" "github.com/carlosframework/platform/internal/rootstate" ) type historyApp struct { Schema int `json:"schema"` Account string `json:"account"` App string `json:"app"` Revision uint64 `json:"revision"` Complete bool `json:"complete"` Hosts []string `json:"hosts"` } func historyAppKey(acct, app string) string { sum := sha256.Sum256([]byte(acct + "\x00" + app)) return hex.EncodeToString(sum[:]) + ".app" } func validHistoryApp(a historyApp) bool { if a.Schema != 1 || !ValidAccount(a.Account) || !ValidApp(a.App) || a.Revision == 0 || len(a.Hosts) > historyLimit { return false } seen := map[string]bool{} for _, host := range a.Hosts { b := HistoryBinding{Host: host, Account: a.Account, App: a.App} if !b.valid() || seen[host] { return false } seen[host] = true } return true } func (h *RootHistory) writeApp(a historyApp) error { if h == nil || h.Store == nil || !validHistoryApp(a) { return os.ErrPermission } raw, err := json.Marshal(a) if err != nil { return err } return h.Store.Write(historyAppKey(a.Account, a.App), raw) } func (h *RootHistory) AppRefusals(acct, app string) ([]Refusal, error) { if err := h.reconstructionReady(); err != nil { return nil, err } if h == nil || h.Store == nil || !ValidAccount(acct) || !ValidApp(app) { return nil, os.ErrPermission } key := historyAppKey(acct, app) raw, err := h.Store.Read(key) if err != nil { return nil, err } var a historyApp if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &a) != nil || !validHistoryApp(a) || !a.Complete || a.Account != acct || a.App != app { return nil, os.ErrPermission } if err := h.Store.Write(key, raw); err != nil { return nil, err } names, err := h.Store.Names() if err != nil { return nil, err } for _, name := range names { if name == rootBakeKey || !strings.HasSuffix(name, ".json") { continue } raw, err := h.Store.Read(name) var r historyRecord if err != nil { return nil, err } if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &r) != nil || !validHistory(r, r.Binding) || historyKey(r.Binding) != name { return nil, os.ErrPermission } if r.Binding.Account == acct && r.Binding.App == app && !slices.Contains(a.Hosts, r.Binding.Host) { return nil, os.ErrPermission } } outcomes, err := h.recoveryOutcomes() if err != nil { return nil, err } for _, r := range outcomes { if r.Binding.Account == acct && r.Binding.App == app && !slices.Contains(a.Hosts, r.Binding.Host) { return nil, os.ErrPermission } } var out []Refusal for _, host := range a.Hosts { refs, err := h.Refusals(HistoryBinding{Host: host, Account: acct, App: app}) if err != nil { return nil, err } out = append(out, refs...) } return out, nil } func (h *RootHistory) MintRefusals(b HistoryBinding) ([]Refusal, error) { if _, err := h.Refusals(b); err != nil { return nil, err } return h.AppRefusals(b.Account, b.App) }