github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/daemon/api_asserts_test.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 daemon_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "io" 27 "net/http" 28 "net/http/httptest" 29 "sort" 30 "strconv" 31 32 "gopkg.in/check.v1" 33 34 "github.com/snapcore/snapd/asserts" 35 "github.com/snapcore/snapd/asserts/assertstest" 36 "github.com/snapcore/snapd/daemon" 37 "github.com/snapcore/snapd/overlord/assertstate" 38 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 39 "github.com/snapcore/snapd/overlord/auth" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 type assertsSuite struct { 44 apiBaseSuite 45 46 mockAssertionFn func(at *asserts.AssertionType, headers []string, user *auth.UserState) (asserts.Assertion, error) 47 48 authUser *auth.UserState 49 } 50 51 var _ = check.Suite(&assertsSuite{}) 52 53 func (s *assertsSuite) SetUpTest(c *check.C) { 54 s.apiBaseSuite.SetUpTest(c) 55 56 s.mockAssertionFn = nil 57 58 d := s.daemonWithStore(c, s) 59 60 st := d.Overlord().State() 61 st.Lock() 62 defer st.Unlock() 63 u, err := auth.NewUser(st, "username", "email@test.com", "macaroon", []string{"discharge"}) 64 c.Assert(err, check.IsNil) 65 s.authUser = u 66 } 67 68 func (s *assertsSuite) userAuth(req *http.Request) { 69 req.Header.Set("Authorization", fmt.Sprintf(`Macaroon root="%s"`, s.authUser.Macaroon)) 70 } 71 72 func (s *assertsSuite) TestGetAsserts(c *check.C) { 73 req, err := http.NewRequest("GET", "/v2/assertions", nil) 74 c.Assert(err, check.IsNil) 75 resp := s.syncReq(c, req, nil) 76 c.Check(resp.Status, check.Equals, 200) 77 c.Check(resp.Result, check.DeepEquals, map[string][]string{"types": asserts.TypeNames()}) 78 } 79 80 func (s *assertsSuite) addAsserts(assertions ...asserts.Assertion) { 81 st := s.d.Overlord().State() 82 st.Lock() 83 defer st.Unlock() 84 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey("")) 85 assertstatetest.AddMany(st, assertions...) 86 } 87 88 func (s *assertsSuite) TestAssertOK(c *check.C) { 89 // add store key 90 s.addAsserts() 91 92 st := s.d.Overlord().State() 93 94 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 95 buf := bytes.NewBuffer(asserts.Encode(acct)) 96 // Execute 97 req, err := http.NewRequest("POST", "/v2/assertions", buf) 98 c.Assert(err, check.IsNil) 99 rsp := s.syncReq(c, req, nil) 100 // Verify (external) 101 c.Check(rsp.Status, check.Equals, 200) 102 // Verify (internal) 103 st.Lock() 104 defer st.Unlock() 105 _, err = assertstate.DB(st).Find(asserts.AccountType, map[string]string{ 106 "account-id": acct.AccountID(), 107 }) 108 c.Check(err, check.IsNil) 109 } 110 111 func (s *assertsSuite) TestAssertStreamOK(c *check.C) { 112 st := s.d.Overlord().State() 113 114 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 115 buf := &bytes.Buffer{} 116 enc := asserts.NewEncoder(buf) 117 err := enc.Encode(acct) 118 c.Assert(err, check.IsNil) 119 err = enc.Encode(s.StoreSigning.StoreAccountKey("")) 120 c.Assert(err, check.IsNil) 121 122 // Execute 123 req, err := http.NewRequest("POST", "/v2/assertions", buf) 124 c.Assert(err, check.IsNil) 125 rsp := s.syncReq(c, req, nil) 126 // Verify (external) 127 c.Check(rsp.Status, check.Equals, 200) 128 // Verify (internal) 129 st.Lock() 130 defer st.Unlock() 131 _, err = assertstate.DB(st).Find(asserts.AccountType, map[string]string{ 132 "account-id": acct.AccountID(), 133 }) 134 c.Check(err, check.IsNil) 135 } 136 137 func (s *assertsSuite) TestAssertInvalid(c *check.C) { 138 // Setup 139 buf := bytes.NewBufferString("blargh") 140 req, err := http.NewRequest("POST", "/v2/assertions", buf) 141 c.Assert(err, check.IsNil) 142 s.userAuth(req) 143 144 rec := httptest.NewRecorder() 145 // Execute 146 s.serveHTTP(c, rec, req) 147 // Verify (external) 148 c.Check(rec.Code, check.Equals, 400) 149 c.Check(rec.Body.String(), testutil.Contains, 150 "cannot decode request body into assertions") 151 } 152 153 func (s *assertsSuite) TestAssertError(c *check.C) { 154 // Setup 155 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 156 buf := bytes.NewBuffer(asserts.Encode(acct)) 157 req, err := http.NewRequest("POST", "/v2/assertions", buf) 158 c.Assert(err, check.IsNil) 159 s.userAuth(req) 160 161 rec := httptest.NewRecorder() 162 // Execute 163 s.serveHTTP(c, rec, req) 164 // Verify (external) 165 c.Check(rec.Code, check.Equals, 400) 166 c.Check(rec.Body.String(), testutil.Contains, "assert failed") 167 } 168 169 func (s *assertsSuite) TestAssertsFindManyAll(c *check.C) { 170 acct := assertstest.NewAccount(s.StoreSigning, "developer1", map[string]interface{}{ 171 "account-id": "developer1-id", 172 }, "") 173 s.addAsserts(acct) 174 175 // Execute 176 req, err := http.NewRequest("GET", "/v2/assertions/account", nil) 177 c.Assert(err, check.IsNil) 178 s.userAuth(req) 179 180 rec := httptest.NewRecorder() 181 s.serveHTTP(c, rec, req) 182 // Verify 183 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 184 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion; bundle=y") 185 c.Check(rec.HeaderMap.Get("X-Ubuntu-Assertions-Count"), check.Equals, "4") 186 dec := asserts.NewDecoder(rec.Body) 187 a1, err := dec.Decode() 188 c.Assert(err, check.IsNil) 189 c.Check(a1.Type(), check.Equals, asserts.AccountType) 190 191 a2, err := dec.Decode() 192 c.Assert(err, check.IsNil) 193 194 a3, err := dec.Decode() 195 c.Assert(err, check.IsNil) 196 197 a4, err := dec.Decode() 198 c.Assert(err, check.IsNil) 199 200 _, err = dec.Decode() 201 c.Assert(err, check.Equals, io.EOF) 202 203 ids := []string{a1.(*asserts.Account).AccountID(), a2.(*asserts.Account).AccountID(), a3.(*asserts.Account).AccountID(), a4.(*asserts.Account).AccountID()} 204 sort.Strings(ids) 205 c.Check(ids, check.DeepEquals, []string{"can0nical", "canonical", "developer1-id", "generic"}) 206 } 207 208 func (s *assertsSuite) TestAssertsFindManyFilter(c *check.C) { 209 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 210 s.addAsserts(acct) 211 212 // Execute 213 req, err := http.NewRequest("GET", "/v2/assertions/account?username=developer1", nil) 214 c.Assert(err, check.IsNil) 215 s.userAuth(req) 216 217 rec := httptest.NewRecorder() 218 s.serveHTTP(c, rec, req) 219 // Verify 220 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 221 c.Check(rec.HeaderMap.Get("X-Ubuntu-Assertions-Count"), check.Equals, "1") 222 dec := asserts.NewDecoder(rec.Body) 223 a1, err := dec.Decode() 224 c.Assert(err, check.IsNil) 225 c.Check(a1.Type(), check.Equals, asserts.AccountType) 226 c.Check(a1.(*asserts.Account).Username(), check.Equals, "developer1") 227 c.Check(a1.(*asserts.Account).AccountID(), check.Equals, acct.AccountID()) 228 _, err = dec.Decode() 229 c.Check(err, check.Equals, io.EOF) 230 } 231 232 func (s *assertsSuite) TestAssertsFindManyNoResults(c *check.C) { 233 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 234 s.addAsserts(acct) 235 236 // Execute 237 req, err := http.NewRequest("GET", "/v2/assertions/account?username=xyzzyx", nil) 238 c.Assert(err, check.IsNil) 239 s.userAuth(req) 240 241 rec := httptest.NewRecorder() 242 s.serveHTTP(c, rec, req) 243 // Verify 244 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 245 c.Check(rec.HeaderMap.Get("X-Ubuntu-Assertions-Count"), check.Equals, "0") 246 dec := asserts.NewDecoder(rec.Body) 247 _, err = dec.Decode() 248 c.Check(err, check.Equals, io.EOF) 249 } 250 251 func (s *assertsSuite) TestAssertsInvalidType(c *check.C) { 252 // Execute 253 req, err := http.NewRequest("GET", "/v2/assertions/foo", nil) 254 c.Assert(err, check.IsNil) 255 s.userAuth(req) 256 257 rec := httptest.NewRecorder() 258 s.serveHTTP(c, rec, req) 259 // Verify 260 c.Check(rec.Code, check.Equals, 400) 261 c.Check(rec.Body.String(), testutil.Contains, "invalid assert type") 262 } 263 264 func (s *assertsSuite) TestAssertsFindManyJSONFilter(c *check.C) { 265 s.testAssertsFindManyJSONFilter(c, "/v2/assertions/account?json=true&username=developer1") 266 } 267 268 func (s *assertsSuite) TestAssertsFindManyJSONFilterRemoteIsFalse(c *check.C) { 269 // setting "remote=false" is the defalt and should not change anything 270 s.testAssertsFindManyJSONFilter(c, "/v2/assertions/account?json=true&username=developer1&remote=false") 271 } 272 273 func (s *assertsSuite) testAssertsFindManyJSONFilter(c *check.C, urlPath string) { 274 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 275 s.addAsserts(acct) 276 277 // Execute 278 req, err := http.NewRequest("GET", urlPath, nil) 279 c.Assert(err, check.IsNil) 280 s.userAuth(req) 281 282 rec := httptest.NewRecorder() 283 s.serveHTTP(c, rec, req) 284 // Verify 285 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 286 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 287 288 var body map[string]interface{} 289 err = json.Unmarshal(rec.Body.Bytes(), &body) 290 c.Assert(err, check.IsNil) 291 c.Check(body["result"], check.DeepEquals, []interface{}{ 292 map[string]interface{}{ 293 "headers": acct.Headers(), 294 }, 295 }) 296 } 297 298 func (s *assertsSuite) TestAssertsFindManyJSONNoResults(c *check.C) { 299 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 300 s.addAsserts(acct) 301 302 // Execute 303 req, err := http.NewRequest("GET", "/v2/assertions/account?json=true&username=xyz", nil) 304 c.Assert(err, check.IsNil) 305 s.userAuth(req) 306 307 rec := httptest.NewRecorder() 308 s.serveHTTP(c, rec, req) 309 // Verify 310 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 311 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 312 313 var body map[string]interface{} 314 err = json.Unmarshal(rec.Body.Bytes(), &body) 315 c.Assert(err, check.IsNil) 316 c.Check(body["result"], check.DeepEquals, []interface{}{}) 317 } 318 319 func (s *assertsSuite) TestAssertsFindManyJSONWithBody(c *check.C) { 320 // add store key 321 s.addAsserts() 322 323 // Execute 324 req, err := http.NewRequest("GET", "/v2/assertions/account-key?json=true", nil) 325 c.Assert(err, check.IsNil) 326 s.userAuth(req) 327 328 rec := httptest.NewRecorder() 329 s.serveHTTP(c, rec, req) 330 // Verify 331 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 332 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 333 334 var got []string 335 var body map[string]interface{} 336 err = json.Unmarshal(rec.Body.Bytes(), &body) 337 c.Assert(err, check.IsNil) 338 for _, a := range body["result"].([]interface{}) { 339 h := a.(map[string]interface{})["headers"].(map[string]interface{}) 340 got = append(got, h["account-id"].(string)+"/"+h["name"].(string)) 341 // check body 342 l, err := strconv.Atoi(h["body-length"].(string)) 343 c.Assert(err, check.IsNil) 344 c.Check(a.(map[string]interface{})["body"], check.HasLen, l) 345 } 346 sort.Strings(got) 347 c.Check(got, check.DeepEquals, []string{"can0nical/root", "can0nical/store", "canonical/root", "generic/models"}) 348 } 349 350 func (s *assertsSuite) TestAssertsFindManyJSONHeadersOnly(c *check.C) { 351 // add store key 352 s.addAsserts() 353 354 // Execute 355 req, err := http.NewRequest("GET", "/v2/assertions/account-key?json=headers&account-id=can0nical", nil) 356 c.Assert(err, check.IsNil) 357 s.userAuth(req) 358 359 rec := httptest.NewRecorder() 360 s.serveHTTP(c, rec, req) 361 // Verify 362 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 363 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 364 365 var got []string 366 var body map[string]interface{} 367 err = json.Unmarshal(rec.Body.Bytes(), &body) 368 c.Assert(err, check.IsNil) 369 for _, a := range body["result"].([]interface{}) { 370 h := a.(map[string]interface{})["headers"].(map[string]interface{}) 371 got = append(got, h["account-id"].(string)+"/"+h["name"].(string)) 372 // check body absent 373 _, ok := a.(map[string]interface{})["body"] 374 c.Assert(ok, check.Equals, false) 375 } 376 sort.Strings(got) 377 c.Check(got, check.DeepEquals, []string{"can0nical/root", "can0nical/store"}) 378 } 379 380 func (s *assertsSuite) TestAssertsFindManyJSONInvalidParam(c *check.C) { 381 // add store key 382 s.addAsserts() 383 384 // Execute 385 req, err := http.NewRequest("GET", "/v2/assertions/account-key?json=header&account-id=can0nical", nil) 386 c.Assert(err, check.IsNil) 387 s.userAuth(req) 388 389 rec := httptest.NewRecorder() 390 s.serveHTTP(c, rec, req) 391 // Verify 392 c.Check(rec.Code, check.Equals, 400, check.Commentf("body %q", rec.Body)) 393 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 394 395 var rsp daemon.Resp 396 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) 397 c.Check(rsp.Status, check.Equals, 400) 398 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 399 c.Check(rsp.Result, check.DeepEquals, map[string]interface{}{ 400 "message": `"json" query parameter when used must be set to "true" or "headers"`, 401 }) 402 } 403 404 func (s *assertsSuite) TestAssertsFindManyJSONNopFilter(c *check.C) { 405 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 406 s.addAsserts(acct) 407 408 // Execute 409 req, err := http.NewRequest("GET", "/v2/assertions/account?json=false&username=developer1", nil) 410 c.Assert(err, check.IsNil) 411 s.userAuth(req) 412 413 rec := httptest.NewRecorder() 414 s.serveHTTP(c, rec, req) 415 // Verify 416 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 417 c.Check(rec.HeaderMap.Get("X-Ubuntu-Assertions-Count"), check.Equals, "1") 418 dec := asserts.NewDecoder(rec.Body) 419 a1, err := dec.Decode() 420 c.Assert(err, check.IsNil) 421 c.Check(a1.Type(), check.Equals, asserts.AccountType) 422 c.Check(a1.(*asserts.Account).Username(), check.Equals, "developer1") 423 c.Check(a1.(*asserts.Account).AccountID(), check.Equals, acct.AccountID()) 424 _, err = dec.Decode() 425 c.Check(err, check.Equals, io.EOF) 426 } 427 428 func (s *assertsSuite) TestAssertsFindManyRemoteInvalidParam(c *check.C) { 429 // Execute 430 req, err := http.NewRequest("GET", "/v2/assertions/account-key?remote=invalid&account-id=can0nical", nil) 431 c.Assert(err, check.IsNil) 432 s.userAuth(req) 433 434 rec := httptest.NewRecorder() 435 s.serveHTTP(c, rec, req) 436 // Verify 437 c.Check(rec.Code, check.Equals, 400, check.Commentf("body %q", rec.Body)) 438 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 439 var rsp daemon.Resp 440 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) 441 c.Check(rsp.Status, check.Equals, 400) 442 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 443 c.Check(rsp.Result, check.DeepEquals, map[string]interface{}{ 444 "message": `"remote" query parameter when used must be set to "true" or "false" or left unset`, 445 }) 446 } 447 448 func (s *assertsSuite) Assertion(at *asserts.AssertionType, headers []string, user *auth.UserState) (asserts.Assertion, error) { 449 return s.mockAssertionFn(at, headers, user) 450 } 451 452 func (s *assertsSuite) TestAssertsFindManyRemote(c *check.C) { 453 var assertFnCalled int 454 s.mockAssertionFn = func(at *asserts.AssertionType, headers []string, user *auth.UserState) (asserts.Assertion, error) { 455 assertFnCalled++ 456 c.Assert(at.Name, check.Equals, "account") 457 c.Assert(headers, check.DeepEquals, []string{"can0nical"}) 458 return assertstest.NewAccount(s.StoreSigning, "some-developer", nil, ""), nil 459 } 460 461 acct := assertstest.NewAccount(s.StoreSigning, "developer1", nil, "") 462 s.addAsserts(acct) 463 464 // Execute 465 req, err := http.NewRequest("GET", "/v2/assertions/account?remote=true&account-id=can0nical", nil) 466 c.Assert(err, check.IsNil) 467 s.userAuth(req) 468 469 rec := httptest.NewRecorder() 470 s.serveHTTP(c, rec, req) 471 // Verify 472 c.Check(assertFnCalled, check.Equals, 1) 473 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 474 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion; bundle=y") 475 476 data := rec.Body.Bytes() 477 c.Check(string(data), check.Matches, `(?ms)type: account 478 authority-id: can0nical 479 account-id: [a-zA-Z0-9]+ 480 display-name: Some-developer 481 timestamp: .* 482 username: some-developer 483 validation: unproven 484 .* 485 `) 486 487 }