github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/storecontext/context.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2019 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 storecontext supplies a pluggable implementation of store.DeviceAndAuthContext. 21 package storecontext 22 23 import ( 24 "fmt" 25 "net/url" 26 "os" 27 28 "github.com/snapcore/snapd/asserts" 29 "github.com/snapcore/snapd/asserts/sysdb" 30 "github.com/snapcore/snapd/overlord/auth" 31 "github.com/snapcore/snapd/overlord/configstate/config" 32 "github.com/snapcore/snapd/overlord/state" 33 "github.com/snapcore/snapd/store" 34 ) 35 36 // A Backend exposes device information and device identity 37 // assertions, signing session requests and proxy store assertion. 38 // Methods can return state.ErrNoState if the underlying needed 39 // information is not (yet) available. They can also assume the state 40 // lock is held. 41 type Backend interface { 42 DeviceBackend 43 44 DeviceSessionRequestSigner 45 46 ProxyStoreer 47 } 48 49 // A DeviceBackend exposes device information and device identity 50 // assertions. 51 // Methods can return state.ErrNoState if the underlying needed 52 // information is not (yet) available. They can also assume the state 53 // lock is held. 54 type DeviceBackend interface { 55 // Device returns current device state. 56 Device() (*auth.DeviceState, error) 57 // SetDevice sets the device details in the state. 58 SetDevice(device *auth.DeviceState) error 59 60 // Model returns the device model assertion. 61 Model() (*asserts.Model, error) 62 // Serial returns the device serial assertion. 63 Serial() (*asserts.Serial, error) 64 } 65 66 type DeviceSessionRequestSigner interface { 67 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 68 SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) 69 } 70 71 type ProxyStoreer interface { 72 // ProxyStore returns the store assertion for the proxy store if one is set. 73 ProxyStore() (*asserts.Store, error) 74 } 75 76 // storeContext implements store.DeviceAndAuthContext. 77 type storeContext struct { 78 state *state.State 79 80 deviceBackend DeviceBackend 81 sessionReqSigner DeviceSessionRequestSigner 82 proxyStoreer ProxyStoreer 83 } 84 85 var _ store.DeviceAndAuthContext = (*storeContext)(nil) 86 87 // New returns a store.DeviceAndAuthContext using the given full-featured Backend. 88 func New(st *state.State, b Backend) store.DeviceAndAuthContext { 89 if b == nil { 90 panic("store context backend cannot be nil") 91 } 92 return NewComposed(st, b, b, b) 93 } 94 95 // NewComposed returns a store.DeviceAndAuthContext using the given backends. 96 func NewComposed(st *state.State, devb DeviceBackend, srqs DeviceSessionRequestSigner, pstoer ProxyStoreer) store.DeviceAndAuthContext { 97 if devb == nil || srqs == nil || pstoer == nil { 98 panic("store context composable backends cannot be nil") 99 } 100 return &storeContext{ 101 state: st, 102 deviceBackend: devb, 103 sessionReqSigner: srqs, 104 proxyStoreer: pstoer, 105 } 106 } 107 108 // Device returns current device state. 109 func (sc *storeContext) Device() (*auth.DeviceState, error) { 110 sc.state.Lock() 111 defer sc.state.Unlock() 112 113 return sc.deviceBackend.Device() 114 } 115 116 // UpdateDeviceAuth updates the device auth details in state. 117 // The last update wins but other device details are left unchanged. 118 // It returns the updated device state value. 119 func (sc *storeContext) UpdateDeviceAuth(device *auth.DeviceState, newSessionMacaroon string) (actual *auth.DeviceState, err error) { 120 sc.state.Lock() 121 defer sc.state.Unlock() 122 123 cur, err := sc.deviceBackend.Device() 124 if err != nil { 125 return nil, err 126 } 127 128 // because of remodeling now more than one place (the global store) 129 // can be trying to set sessions, don't update if the original session 130 // doesn't match 131 if cur.SessionMacaroon != device.SessionMacaroon { 132 // nothing to do 133 return cur, nil 134 } 135 136 cur.SessionMacaroon = newSessionMacaroon 137 if err := sc.deviceBackend.SetDevice(cur); err != nil { 138 return nil, fmt.Errorf("internal error: cannot update just read device state: %v", err) 139 } 140 141 return cur, nil 142 } 143 144 // UpdateUserAuth updates the user auth details in state. 145 // The last update wins but other user details are left unchanged. 146 // It returns the updated user state value. 147 func (sc *storeContext) UpdateUserAuth(user *auth.UserState, newDischarges []string) (actual *auth.UserState, err error) { 148 sc.state.Lock() 149 defer sc.state.Unlock() 150 151 cur, err := auth.User(sc.state, user.ID) 152 if err != nil { 153 return nil, err 154 } 155 156 // just do it, last update wins 157 cur.StoreDischarges = newDischarges 158 if err := auth.UpdateUser(sc.state, cur); err != nil { 159 return nil, fmt.Errorf("internal error: cannot update just read user state: %v", err) 160 } 161 162 return cur, nil 163 } 164 165 // StoreID returns the store set in the model assertion, if mod != nil 166 // and it's not the generic classic model, or the override from the 167 // UBUNTU_STORE_ID envvar. 168 func StoreID(mod *asserts.Model) string { 169 if mod != nil && mod.Ref().Unique() != sysdb.GenericClassicModel().Ref().Unique() { 170 return mod.Store() 171 } 172 return os.Getenv("UBUNTU_STORE_ID") 173 } 174 175 // StoreID returns the store id according to system state or 176 // the fallback one if the state has none set (yet). 177 func (sc *storeContext) StoreID(fallback string) (string, error) { 178 sc.state.Lock() 179 defer sc.state.Unlock() 180 181 mod, err := sc.deviceBackend.Model() 182 if err != nil && err != state.ErrNoState { 183 return "", err 184 } 185 186 storeID := StoreID(mod) 187 if storeID != "" { 188 return storeID, nil 189 } 190 191 return fallback, nil 192 } 193 194 type DeviceSessionRequestParams = store.DeviceSessionRequestParams 195 196 // DeviceSessionRequestParams produces a device-session-request with the given nonce, together with other required parameters, the device serial and model assertions. It returns store.ErrNoSerial if the device serial is not yet initialized. 197 func (sc *storeContext) DeviceSessionRequestParams(nonce string) (*DeviceSessionRequestParams, error) { 198 sc.state.Lock() 199 defer sc.state.Unlock() 200 201 params, err := sc.deviceSessionRequestParams(nonce) 202 if err == state.ErrNoState { 203 return nil, store.ErrNoSerial 204 } 205 206 return params, err 207 } 208 209 func (sc *storeContext) deviceSessionRequestParams(nonce string) (*DeviceSessionRequestParams, error) { 210 model, err := sc.deviceBackend.Model() 211 if err != nil { 212 return nil, err 213 } 214 215 serial, err := sc.deviceBackend.Serial() 216 if err != nil { 217 return nil, err 218 } 219 220 deviceSessionReq, err := sc.sessionReqSigner.SignDeviceSessionRequest(serial, nonce) 221 if err != nil { 222 return nil, err 223 } 224 225 return &DeviceSessionRequestParams{ 226 Request: deviceSessionReq, 227 Serial: serial, 228 Model: model, 229 }, nil 230 } 231 232 // ProxyStoreParams returns the id and URL of the proxy store if one is set. Returns the defaultURL otherwise and id = "". 233 func (sc *storeContext) ProxyStoreParams(defaultURL *url.URL) (proxyStoreID string, proxySroreURL *url.URL, err error) { 234 sc.state.Lock() 235 defer sc.state.Unlock() 236 237 sto, err := sc.proxyStoreer.ProxyStore() 238 if err != nil && err != state.ErrNoState { 239 return "", nil, err 240 } 241 242 if sto != nil { 243 return sto.Store(), sto.URL(), nil 244 } 245 246 return "", defaultURL, nil 247 } 248 249 // CloudInfo returns the cloud instance information (if available). 250 func (sc *storeContext) CloudInfo() (*auth.CloudInfo, error) { 251 sc.state.Lock() 252 defer sc.state.Unlock() 253 254 tr := config.NewTransaction(sc.state) 255 var cloudInfo auth.CloudInfo 256 err := tr.Get("core", "cloud", &cloudInfo) 257 if err != nil && !config.IsNoOption(err) { 258 return nil, err 259 } 260 261 if cloudInfo.Name != "" { 262 return &cloudInfo, nil 263 } 264 265 return nil, nil 266 }