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