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