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 }