github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/store/store_asserts_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 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 store_test
    21  
    22  import (
    23  	"bytes"
    24  	"io"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"net/url"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/asserts/assertstest"
    34  	"github.com/snapcore/snapd/store"
    35  )
    36  
    37  type storeAssertsSuite struct {
    38  	baseStoreSuite
    39  
    40  	storeSigning *assertstest.StoreStack
    41  	dev1Acct     *asserts.Account
    42  	decl1        *asserts.SnapDeclaration
    43  
    44  	db *asserts.Database
    45  }
    46  
    47  var _ = Suite(&storeAssertsSuite{})
    48  
    49  func (s *storeAssertsSuite) SetUpTest(c *C) {
    50  	s.baseStoreSuite.SetUpTest(c)
    51  
    52  	s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
    53  	s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", map[string]interface{}{
    54  		"account-id": "developer1",
    55  	}, "")
    56  
    57  	a, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
    58  		"series":       "16",
    59  		"snap-id":      "asnapid",
    60  		"snap-name":    "asnap",
    61  		"publisher-id": "developer1",
    62  		"timestamp":    time.Now().UTC().Format(time.RFC3339),
    63  	}, nil, "")
    64  	c.Assert(err, IsNil)
    65  	s.decl1 = a.(*asserts.SnapDeclaration)
    66  
    67  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    68  		Backstore: asserts.NewMemoryBackstore(),
    69  		Trusted:   s.storeSigning.Trusted,
    70  	})
    71  	c.Assert(err, IsNil)
    72  	s.db = db
    73  }
    74  
    75  var testAssertion = `type: snap-declaration
    76  authority-id: super
    77  series: 16
    78  snap-id: snapidfoo
    79  publisher-id: devidbaz
    80  snap-name: mysnap
    81  timestamp: 2016-03-30T12:22:16Z
    82  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
    83  
    84  openpgp wsBcBAABCAAQBQJW+8VBCRDWhXkqAWcrfgAAQ9gIABZFgMPByJZeUE835FkX3/y2hORn
    85  AzE3R1ktDkQEVe/nfVDMACAuaw1fKmUS4zQ7LIrx/AZYw5i0vKVmJszL42LBWVsqR0+p9Cxebzv9
    86  U2VUSIajEsUUKkBwzD8wxFzagepFlScif1NvCGZx0vcGUOu0Ent0v+gqgAv21of4efKqEW7crlI1
    87  T/A8LqZYmIzKRHGwCVucCyAUD8xnwt9nyWLgLB+LLPOVFNK8SR6YyNsX05Yz1BUSndBfaTN8j/k8
    88  8isKGZE6P0O9ozBbNIAE8v8NMWQegJ4uWuil7D3psLkzQIrxSypk9TrQ2GlIG2hJdUovc5zBuroe
    89  xS4u9rVT6UY=`
    90  
    91  func (s *storeAssertsSuite) TestAssertion(c *C) {
    92  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 88)
    93  	defer restore()
    94  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    95  		assertRequest(c, r, "GET", "/api/v1/snaps/assertions/.*")
    96  		// check device authorization is set, implicitly checking doRequest was used
    97  		c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
    98  
    99  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   100  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/snapidfoo")
   101  		c.Check(r.URL.Query().Get("max-format"), Equals, "88")
   102  		io.WriteString(w, testAssertion)
   103  	}))
   104  
   105  	c.Assert(mockServer, NotNil)
   106  	defer mockServer.Close()
   107  
   108  	mockServerURL, _ := url.Parse(mockServer.URL)
   109  	cfg := store.Config{
   110  		StoreBaseURL: mockServerURL,
   111  	}
   112  	dauthCtx := &testDauthContext{c: c, device: s.device}
   113  	sto := store.New(&cfg, dauthCtx)
   114  
   115  	a, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   116  	c.Assert(err, IsNil)
   117  	c.Check(a, NotNil)
   118  	c.Check(a.Type(), Equals, asserts.SnapDeclarationType)
   119  }
   120  
   121  func (s *storeAssertsSuite) TestAssertionProxyStoreFromAuthContext(c *C) {
   122  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 88)
   123  	defer restore()
   124  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   125  		assertRequest(c, r, "GET", "/api/v1/snaps/assertions/.*")
   126  		// check device authorization is set, implicitly checking doRequest was used
   127  		c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   128  
   129  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   130  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/snapidfoo")
   131  		c.Check(r.URL.Query().Get("max-format"), Equals, "88")
   132  		io.WriteString(w, testAssertion)
   133  	}))
   134  
   135  	c.Assert(mockServer, NotNil)
   136  	defer mockServer.Close()
   137  
   138  	mockServerURL, _ := url.Parse(mockServer.URL)
   139  	nowhereURL, err := url.Parse("http://nowhere.invalid")
   140  	c.Assert(err, IsNil)
   141  	cfg := store.Config{
   142  		AssertionsBaseURL: nowhereURL,
   143  	}
   144  	dauthCtx := &testDauthContext{
   145  		c:             c,
   146  		device:        s.device,
   147  		proxyStoreID:  "foo",
   148  		proxyStoreURL: mockServerURL,
   149  	}
   150  	sto := store.New(&cfg, dauthCtx)
   151  
   152  	a, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   153  	c.Assert(err, IsNil)
   154  	c.Check(a, NotNil)
   155  	c.Check(a.Type(), Equals, asserts.SnapDeclarationType)
   156  }
   157  
   158  func (s *storeAssertsSuite) TestAssertionNotFound(c *C) {
   159  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   160  		assertRequest(c, r, "GET", "/api/v1/snaps/assertions/.*")
   161  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   162  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/snapidfoo")
   163  		w.Header().Set("Content-Type", "application/problem+json")
   164  		w.WriteHeader(404)
   165  		io.WriteString(w, `{"status": 404,"title": "not found"}`)
   166  	}))
   167  
   168  	c.Assert(mockServer, NotNil)
   169  	defer mockServer.Close()
   170  
   171  	mockServerURL, _ := url.Parse(mockServer.URL)
   172  	cfg := store.Config{
   173  		AssertionsBaseURL: mockServerURL,
   174  	}
   175  	sto := store.New(&cfg, nil)
   176  
   177  	_, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   178  	c.Check(asserts.IsNotFound(err), Equals, true)
   179  	c.Check(err, DeepEquals, &asserts.NotFoundError{
   180  		Type: asserts.SnapDeclarationType,
   181  		Headers: map[string]string{
   182  			"series":  "16",
   183  			"snap-id": "snapidfoo",
   184  		},
   185  	})
   186  }
   187  
   188  func (s *storeAssertsSuite) TestAssertion500(c *C) {
   189  	var n = 0
   190  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   191  		assertRequest(c, r, "GET", "/api/v1/snaps/assertions/.*")
   192  		n++
   193  		w.WriteHeader(500)
   194  	}))
   195  
   196  	c.Assert(mockServer, NotNil)
   197  	defer mockServer.Close()
   198  
   199  	mockServerURL, _ := url.Parse(mockServer.URL)
   200  	cfg := store.Config{
   201  		AssertionsBaseURL: mockServerURL,
   202  	}
   203  	sto := store.New(&cfg, nil)
   204  
   205  	_, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   206  	c.Assert(err, ErrorMatches, `cannot fetch assertion: got unexpected HTTP status code 500 via .+`)
   207  	c.Assert(n, Equals, 5)
   208  }
   209  
   210  func (s *storeAssertsSuite) TestDownloadAssertionsSimple(c *C) {
   211  	assertstest.AddMany(s.db, s.storeSigning.StoreAccountKey(""), s.dev1Acct)
   212  
   213  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   214  		assertRequest(c, r, "GET", "/assertions/.*")
   215  		// check device authorization is set, implicitly checking doRequest was used
   216  		c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   217  
   218  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   219  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/asnapid")
   220  		c.Check(r.URL.Query().Get("max-format"), Equals, "88")
   221  		w.Write(asserts.Encode(s.decl1))
   222  	}))
   223  
   224  	c.Assert(mockServer, NotNil)
   225  	defer mockServer.Close()
   226  
   227  	mockServerURL, _ := url.Parse(mockServer.URL)
   228  	cfg := store.Config{
   229  		StoreBaseURL: mockServerURL,
   230  	}
   231  
   232  	dauthCtx := &testDauthContext{c: c, device: s.device}
   233  	sto := store.New(&cfg, dauthCtx)
   234  
   235  	streamURL, err := mockServerURL.Parse("/assertions/snap-declaration/16/asnapid")
   236  	c.Assert(err, IsNil)
   237  	urls := []string{streamURL.String() + "?max-format=88"}
   238  
   239  	b := asserts.NewBatch(nil)
   240  	err = sto.DownloadAssertions(urls, b, nil)
   241  	c.Assert(err, IsNil)
   242  
   243  	c.Assert(b.CommitTo(s.db, nil), IsNil)
   244  
   245  	// added
   246  	_, err = s.decl1.Ref().Resolve(s.db.Find)
   247  	c.Check(err, IsNil)
   248  }
   249  
   250  func (s *storeAssertsSuite) TestDownloadAssertionsWithStreams(c *C) {
   251  	stream1 := append(asserts.Encode(s.decl1), "\n"...)
   252  	stream1 = append(stream1, asserts.Encode(s.dev1Acct)...)
   253  	stream2 := asserts.Encode(s.storeSigning.StoreAccountKey(""))
   254  
   255  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   256  		assertRequest(c, r, "GET", "/assertions/.*")
   257  
   258  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   259  		var stream []byte
   260  		switch r.URL.Path {
   261  		case "/assertions/stream1":
   262  			stream = stream1
   263  		case "/assertions/stream2":
   264  			stream = stream2
   265  		default:
   266  			c.Fatal("unexpected stream url")
   267  		}
   268  
   269  		w.Write(stream)
   270  	}))
   271  
   272  	c.Assert(mockServer, NotNil)
   273  	defer mockServer.Close()
   274  
   275  	mockServerURL, _ := url.Parse(mockServer.URL)
   276  	cfg := store.Config{
   277  		StoreBaseURL: mockServerURL,
   278  	}
   279  
   280  	dauthCtx := &testDauthContext{c: c, device: s.device}
   281  	sto := store.New(&cfg, dauthCtx)
   282  
   283  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   284  	c.Assert(err, IsNil)
   285  	stream2URL, err := mockServerURL.Parse("/assertions/stream2")
   286  	c.Assert(err, IsNil)
   287  
   288  	urls := []string{stream1URL.String(), stream2URL.String()}
   289  
   290  	b := asserts.NewBatch(nil)
   291  	err = sto.DownloadAssertions(urls, b, nil)
   292  	c.Assert(err, IsNil)
   293  
   294  	c.Assert(b.CommitTo(s.db, nil), IsNil)
   295  
   296  	// added
   297  	_, err = s.decl1.Ref().Resolve(s.db.Find)
   298  	c.Check(err, IsNil)
   299  }
   300  
   301  func (s *storeAssertsSuite) TestDownloadAssertionsBrokenStream(c *C) {
   302  	stream1 := append(asserts.Encode(s.decl1), "\n"...)
   303  	stream1 = append(stream1, asserts.Encode(s.dev1Acct)...)
   304  
   305  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   306  		assertRequest(c, r, "GET", "/assertions/stream1")
   307  
   308  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   309  
   310  		breakAt := bytes.Index(stream1, []byte("account-id"))
   311  		w.Write(stream1[:breakAt])
   312  	}))
   313  
   314  	c.Assert(mockServer, NotNil)
   315  	defer mockServer.Close()
   316  
   317  	mockServerURL, _ := url.Parse(mockServer.URL)
   318  	cfg := store.Config{
   319  		StoreBaseURL: mockServerURL,
   320  	}
   321  
   322  	dauthCtx := &testDauthContext{c: c, device: s.device}
   323  	sto := store.New(&cfg, dauthCtx)
   324  
   325  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   326  	c.Assert(err, IsNil)
   327  
   328  	urls := []string{stream1URL.String()}
   329  
   330  	b := asserts.NewBatch(nil)
   331  	err = sto.DownloadAssertions(urls, b, nil)
   332  	c.Assert(err, Equals, io.ErrUnexpectedEOF)
   333  }
   334  
   335  func (s *storeAssertsSuite) TestDownloadAssertions500(c *C) {
   336  	n := 0
   337  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   338  		assertRequest(c, r, "GET", "/assertions/stream1")
   339  
   340  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   341  
   342  		n++
   343  		w.WriteHeader(500)
   344  	}))
   345  
   346  	c.Assert(mockServer, NotNil)
   347  	defer mockServer.Close()
   348  
   349  	mockServerURL, _ := url.Parse(mockServer.URL)
   350  	cfg := store.Config{
   351  		StoreBaseURL: mockServerURL,
   352  	}
   353  
   354  	dauthCtx := &testDauthContext{c: c, device: s.device}
   355  	sto := store.New(&cfg, dauthCtx)
   356  
   357  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   358  	c.Assert(err, IsNil)
   359  
   360  	urls := []string{stream1URL.String()}
   361  
   362  	b := asserts.NewBatch(nil)
   363  	err = sto.DownloadAssertions(urls, b, nil)
   364  	c.Assert(err, ErrorMatches, `cannot download assertion stream: got unexpected HTTP status code 500 via .+`)
   365  	c.Check(n, Equals, 5)
   366  }
   367  
   368  func (s *storeAssertsSuite) TestDownloadAssertionsStreamNotFound(c *C) {
   369  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   370  		assertRequest(c, r, "GET", "/assertions/stream1")
   371  
   372  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   373  		w.Header().Set("Content-Type", "application/problem+json")
   374  		w.WriteHeader(404)
   375  		io.WriteString(w, `{"status": 404,"title": "not found"}`)
   376  	}))
   377  
   378  	c.Assert(mockServer, NotNil)
   379  	defer mockServer.Close()
   380  
   381  	mockServerURL, _ := url.Parse(mockServer.URL)
   382  	cfg := store.Config{
   383  		StoreBaseURL: mockServerURL,
   384  	}
   385  
   386  	dauthCtx := &testDauthContext{c: c, device: s.device}
   387  	sto := store.New(&cfg, dauthCtx)
   388  
   389  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   390  	c.Assert(err, IsNil)
   391  
   392  	urls := []string{stream1URL.String()}
   393  
   394  	b := asserts.NewBatch(nil)
   395  	err = sto.DownloadAssertions(urls, b, nil)
   396  	c.Assert(err, ErrorMatches, `assertion service error: \[not found\].*`)
   397  }