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  }