github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/store_asserts.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "fmt" 24 "net/url" 25 "time" 26 ) 27 28 // Store holds a store assertion, defining the configuration needed to connect 29 // a device to the store or relative to a non-default store. 30 type Store struct { 31 assertionBase 32 url *url.URL 33 friendlyStores []string 34 timestamp time.Time 35 } 36 37 // Store returns the identifying name of the operator's store. 38 func (store *Store) Store() string { 39 return store.HeaderString("store") 40 } 41 42 // OperatorID returns the account id of the store's operator. 43 func (store *Store) OperatorID() string { 44 return store.HeaderString("operator-id") 45 } 46 47 // URL returns the URL of the store's API. 48 func (store *Store) URL() *url.URL { 49 return store.url 50 } 51 52 // FriendlyStores returns stores holding snaps that are also exposed 53 // through this one. 54 func (store *Store) FriendlyStores() []string { 55 return store.friendlyStores 56 } 57 58 // Location returns a summary of the store's location/purpose. 59 func (store *Store) Location() string { 60 return store.HeaderString("location") 61 } 62 63 // Timestamp returns the time when the store assertion was issued. 64 func (store *Store) Timestamp() time.Time { 65 return store.timestamp 66 } 67 68 func (store *Store) checkConsistency(db RODatabase, acck *AccountKey) error { 69 // Will be applied to a system's snapd or influence snapd 70 // policy decisions (via friendly-stores) so must be signed by a trusted 71 // authority! 72 if !db.IsTrustedAccount(store.AuthorityID()) { 73 return fmt.Errorf("store assertion %q is not signed by a directly trusted authority: %s", 74 store.Store(), store.AuthorityID()) 75 } 76 77 _, err := db.Find(AccountType, map[string]string{"account-id": store.OperatorID()}) 78 if err != nil { 79 if IsNotFound(err) { 80 return fmt.Errorf( 81 "store assertion %q does not have a matching account assertion for the operator %q", 82 store.Store(), store.OperatorID()) 83 } 84 return err 85 } 86 87 return nil 88 } 89 90 // Prerequisites returns references to this store's prerequisite assertions. 91 func (store *Store) Prerequisites() []*Ref { 92 return []*Ref{ 93 {AccountType, []string{store.OperatorID()}}, 94 } 95 } 96 97 // checkStoreURL validates the "url" header and returns a full URL or nil. 98 func checkStoreURL(headers map[string]interface{}) (*url.URL, error) { 99 s, err := checkOptionalString(headers, "url") 100 if err != nil { 101 return nil, err 102 } 103 104 if s == "" { 105 return nil, nil 106 } 107 108 errWhat := `"url" header` 109 110 u, err := url.Parse(s) 111 if err != nil { 112 return nil, fmt.Errorf("%s must be a valid URL: %s", errWhat, s) 113 } 114 if u.Scheme != "http" && u.Scheme != "https" { 115 return nil, fmt.Errorf(`%s scheme must be "https" or "http": %s`, errWhat, s) 116 } 117 if u.Host == "" { 118 return nil, fmt.Errorf(`%s must have a host: %s`, errWhat, s) 119 } 120 if u.RawQuery != "" { 121 return nil, fmt.Errorf(`%s must not have a query: %s`, errWhat, s) 122 } 123 if u.Fragment != "" { 124 return nil, fmt.Errorf(`%s must not have a fragment: %s`, errWhat, s) 125 } 126 127 return u, nil 128 } 129 130 func assembleStore(assert assertionBase) (Assertion, error) { 131 _, err := checkNotEmptyString(assert.headers, "operator-id") 132 if err != nil { 133 return nil, err 134 } 135 136 url, err := checkStoreURL(assert.headers) 137 if err != nil { 138 return nil, err 139 } 140 141 friendlyStores, err := checkStringList(assert.headers, "friendly-stores") 142 if err != nil { 143 return nil, err 144 } 145 146 _, err = checkOptionalString(assert.headers, "location") 147 if err != nil { 148 return nil, err 149 } 150 151 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 152 if err != nil { 153 return nil, err 154 } 155 156 return &Store{ 157 assertionBase: assert, 158 url: url, 159 friendlyStores: friendlyStores, 160 timestamp: timestamp, 161 }, nil 162 }