github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  }