github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/client/asserts.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 client
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"fmt"
    26  	"io"
    27  	"net/url"
    28  	"strconv"
    29  
    30  	"golang.org/x/xerrors"
    31  
    32  	"github.com/snapcore/snapd/asserts" // for parsing
    33  	"github.com/snapcore/snapd/snap"
    34  )
    35  
    36  // Ack tries to add an assertion to the system assertion
    37  // database. To succeed the assertion must be valid, its signature
    38  // verified with a known public key and the assertion consistent with
    39  // and its prerequisite in the database.
    40  func (client *Client) Ack(b []byte) error {
    41  	var rsp interface{}
    42  	if _, err := client.doSync("POST", "/v2/assertions", nil, nil, bytes.NewReader(b), &rsp); err != nil {
    43  		return err
    44  	}
    45  
    46  	return nil
    47  }
    48  
    49  // AssertionTypes returns a list of assertion type names.
    50  func (client *Client) AssertionTypes() ([]string, error) {
    51  	var types struct {
    52  		Types []string `json:"types"`
    53  	}
    54  	_, err := client.doSync("GET", "/v2/assertions", nil, nil, nil, &types)
    55  	if err != nil {
    56  		fmt := "cannot get assertion type names: %w"
    57  		return nil, xerrors.Errorf(fmt, err)
    58  	}
    59  
    60  	return types.Types, nil
    61  }
    62  
    63  // KnownOptions represent the options of the Known call.
    64  type KnownOptions struct {
    65  	// If Remote is true, the store is queried to find the assertion
    66  	Remote bool
    67  }
    68  
    69  // Known queries assertions with type assertTypeName and matching assertion headers.
    70  func (client *Client) Known(assertTypeName string, headers map[string]string, opts *KnownOptions) ([]asserts.Assertion, error) {
    71  	if opts == nil {
    72  		opts = &KnownOptions{}
    73  	}
    74  
    75  	path := fmt.Sprintf("/v2/assertions/%s", assertTypeName)
    76  	q := url.Values{}
    77  
    78  	if len(headers) > 0 {
    79  		for k, v := range headers {
    80  			q.Set(k, v)
    81  		}
    82  	}
    83  	if opts.Remote {
    84  		q.Set("remote", "true")
    85  	}
    86  
    87  	response, cancel, err := client.rawWithTimeout(context.Background(), "GET", path, q, nil, nil, nil)
    88  	if err != nil {
    89  		fmt := "failed to query assertions: %w"
    90  		return nil, xerrors.Errorf(fmt, err)
    91  	}
    92  	defer cancel()
    93  	defer response.Body.Close()
    94  	if response.StatusCode != 200 {
    95  		return nil, parseError(response)
    96  	}
    97  
    98  	sanityCount, err := strconv.Atoi(response.Header.Get("X-Ubuntu-Assertions-Count"))
    99  	if err != nil {
   100  		return nil, fmt.Errorf("invalid assertions count")
   101  	}
   102  
   103  	dec := asserts.NewDecoder(response.Body)
   104  
   105  	asserts := []asserts.Assertion{}
   106  
   107  	// TODO: make sure asserts can decode and deal with unknown types
   108  	for {
   109  		a, err := dec.Decode()
   110  		if err == io.EOF {
   111  			break
   112  		}
   113  		if err != nil {
   114  			return nil, fmt.Errorf("failed to decode assertions: %v", err)
   115  		}
   116  		asserts = append(asserts, a)
   117  	}
   118  
   119  	if len(asserts) != sanityCount {
   120  		return nil, fmt.Errorf("response did not have the expected number of assertions")
   121  	}
   122  
   123  	return asserts, nil
   124  }
   125  
   126  // StoreAccount returns the full store account info for the specified accountID
   127  func (client *Client) StoreAccount(accountID string) (*snap.StoreAccount, error) {
   128  	assertions, err := client.Known("account", map[string]string{"account-id": accountID}, nil)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	switch len(assertions) {
   133  	case 1:
   134  		// happy case, break out of the switch
   135  	case 0:
   136  		return nil, fmt.Errorf("no assertion found for account-id %s", accountID)
   137  	default:
   138  		// unknown how this could happen...
   139  		return nil, fmt.Errorf("multiple assertions for account-id %s", accountID)
   140  	}
   141  
   142  	acct, ok := assertions[0].(*asserts.Account)
   143  	if !ok {
   144  		return nil, fmt.Errorf("incorrect type of account assertion returned")
   145  	}
   146  	return &snap.StoreAccount{
   147  		ID:          acct.AccountID(),
   148  		Username:    acct.Username(),
   149  		DisplayName: acct.DisplayName(),
   150  		Validation:  acct.Validation(),
   151  	}, nil
   152  }