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  }