github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/daemon/api_validate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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 daemon_test
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"sort"
    27  	"strings"
    28  
    29  	"gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/asserts/assertstest"
    33  	"github.com/snapcore/snapd/asserts/snapasserts"
    34  	"github.com/snapcore/snapd/daemon"
    35  	"github.com/snapcore/snapd/overlord/assertstate"
    36  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    37  	"github.com/snapcore/snapd/overlord/auth"
    38  	"github.com/snapcore/snapd/overlord/snapstate"
    39  	"github.com/snapcore/snapd/overlord/state"
    40  	"github.com/snapcore/snapd/release"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/naming"
    43  )
    44  
    45  var _ = check.Suite(&apiValidationSetsSuite{})
    46  
    47  type apiValidationSetsSuite struct {
    48  	apiBaseSuite
    49  
    50  	storeSigning              *assertstest.StoreStack
    51  	dev1Signing               *assertstest.SigningDB
    52  	dev1acct                  *asserts.Account
    53  	acct1Key                  *asserts.AccountKey
    54  	mockSeqFormingAssertionFn func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error)
    55  }
    56  
    57  type byName []*snapasserts.InstalledSnap
    58  
    59  func (b byName) Len() int      { return len(b) }
    60  func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
    61  func (b byName) Less(i, j int) bool {
    62  	return b[i].SnapName() < b[j].SnapName()
    63  }
    64  
    65  func (s *apiValidationSetsSuite) SetUpTest(c *check.C) {
    66  	s.apiBaseSuite.SetUpTest(c)
    67  	d := s.daemon(c)
    68  
    69  	s.expectAuthenticatedAccess()
    70  
    71  	restore := asserts.MockMaxSupportedFormat(asserts.ValidationSetType, 1)
    72  	s.AddCleanup(restore)
    73  
    74  	s.mockSeqFormingAssertionFn = nil
    75  
    76  	s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
    77  
    78  	st := d.Overlord().State()
    79  	st.Lock()
    80  	snapstate.ReplaceStore(st, s)
    81  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
    82  	st.Unlock()
    83  
    84  	s.dev1acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
    85  	c.Assert(s.storeSigning.Add(s.dev1acct), check.IsNil)
    86  
    87  	// developer signing
    88  	dev1PrivKey, _ := assertstest.GenerateKey(752)
    89  	s.acct1Key = assertstest.NewAccountKey(s.storeSigning, s.dev1acct, nil, dev1PrivKey.PublicKey(), "")
    90  
    91  	s.dev1Signing = assertstest.NewSigningDB(s.dev1acct.AccountID(), dev1PrivKey)
    92  	c.Assert(s.storeSigning.Add(s.acct1Key), check.IsNil)
    93  
    94  	d.Overlord().Loop()
    95  	s.AddCleanup(func() { d.Overlord().Stop() })
    96  }
    97  
    98  func (s *apiValidationSetsSuite) mockValidationSetsTracking(st *state.State) {
    99  	st.Set("validation-sets", map[string]interface{}{
   100  		fmt.Sprintf("%s/foo", s.dev1acct.AccountID()): map[string]interface{}{
   101  			"account-id": s.dev1acct.AccountID(),
   102  			"name":       "foo",
   103  			"mode":       assertstate.Enforce,
   104  			"pinned-at":  9,
   105  			"current":    99,
   106  		},
   107  		fmt.Sprintf("%s/baz", s.dev1acct.AccountID()): map[string]interface{}{
   108  			"account-id": s.dev1acct.AccountID(),
   109  			"name":       "baz",
   110  			"mode":       assertstate.Monitor,
   111  			"pinned-at":  0,
   112  			"current":    2,
   113  		},
   114  	})
   115  }
   116  
   117  func (s *apiValidationSetsSuite) mockAssert(c *check.C, name, sequence string) asserts.Assertion {
   118  	snaps := []interface{}{map[string]interface{}{
   119  		"id":       "yOqKhntON3vR7kwEbVPsILm7bUViPDzz",
   120  		"name":     "snap-b",
   121  		"presence": "required",
   122  		"revision": "1",
   123  	}}
   124  	headers := map[string]interface{}{
   125  		"authority-id": s.dev1acct.AccountID(),
   126  		"account-id":   s.dev1acct.AccountID(),
   127  		"name":         name,
   128  		"series":       "16",
   129  		"sequence":     sequence,
   130  		"revision":     "5",
   131  		"timestamp":    "2030-11-06T09:16:26Z",
   132  		"snaps":        snaps,
   133  	}
   134  	vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "")
   135  	c.Assert(err, check.IsNil)
   136  	return vs
   137  }
   138  
   139  func (s *apiValidationSetsSuite) SeqFormingAssertion(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   140  	s.pokeStateLock()
   141  	return s.mockSeqFormingAssertionFn(assertType, sequenceKey, sequence, user)
   142  }
   143  
   144  func (s *apiValidationSetsSuite) TestQueryValidationSetsErrors(c *check.C) {
   145  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   146  		return nil, &asserts.NotFoundError{
   147  			Type: assertType,
   148  		}
   149  	}
   150  
   151  	st := s.d.Overlord().State()
   152  	st.Lock()
   153  	s.mockValidationSetsTracking(st)
   154  	st.Unlock()
   155  
   156  	for i, tc := range []struct {
   157  		validationSet string
   158  		// sequence is normally an int, use string for passing invalid ones.
   159  		sequence string
   160  		message  string
   161  		status   int
   162  	}{
   163  		{
   164  			validationSet: "abc/Xfoo",
   165  			message:       `invalid name "Xfoo"`,
   166  			status:        400,
   167  		},
   168  		{
   169  			validationSet: "Xfoo/bar",
   170  			message:       `invalid account ID "Xfoo"`,
   171  			status:        400,
   172  		},
   173  		{
   174  			validationSet: "foo/foo",
   175  			message:       "validation set not found",
   176  			status:        404,
   177  		},
   178  		{
   179  			validationSet: "foo/bar",
   180  			sequence:      "1999",
   181  			message:       "validation set not found",
   182  			status:        404,
   183  		},
   184  		{
   185  			validationSet: "foo/bar",
   186  			sequence:      "x",
   187  			message:       "invalid sequence argument",
   188  			status:        400,
   189  		},
   190  		{
   191  			validationSet: "foo/bar",
   192  			sequence:      "-2",
   193  			message:       "invalid sequence argument: -2",
   194  			status:        400,
   195  		},
   196  	} {
   197  		q := url.Values{}
   198  		if tc.sequence != "" {
   199  			q.Set("sequence", tc.sequence)
   200  		}
   201  		req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s?%s", tc.validationSet, q.Encode()), nil)
   202  		c.Assert(err, check.IsNil)
   203  		rspe := s.errorReq(c, req, nil)
   204  		c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i))
   205  		c.Check(rspe.Message, check.Matches, tc.message)
   206  	}
   207  }
   208  
   209  func (s *apiValidationSetsSuite) TestGetValidationSetsNone(c *check.C) {
   210  	req, err := http.NewRequest("GET", "/v2/validation-sets", nil)
   211  	c.Assert(err, check.IsNil)
   212  
   213  	rsp := s.syncReq(c, req, nil)
   214  	c.Assert(rsp.Status, check.Equals, 200)
   215  	res := rsp.Result.([]daemon.ValidationSetResult)
   216  	c.Check(res, check.HasLen, 0)
   217  }
   218  
   219  func (s *apiValidationSetsSuite) TestListValidationSets(c *check.C) {
   220  	st := s.d.Overlord().State()
   221  	st.Lock()
   222  	s.mockValidationSetsTracking(st)
   223  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   224  	as := s.mockAssert(c, "foo", "9")
   225  	err := assertstate.Add(st, as)
   226  	c.Check(err, check.IsNil)
   227  	as = s.mockAssert(c, "baz", "2")
   228  	err = assertstate.Add(st, as)
   229  	st.Unlock()
   230  	c.Assert(err, check.IsNil)
   231  
   232  	req, err := http.NewRequest("GET", "/v2/validation-sets", nil)
   233  	c.Assert(err, check.IsNil)
   234  	rsp := s.syncReq(c, req, nil)
   235  	c.Assert(rsp.Status, check.Equals, 200)
   236  	res := rsp.Result.([]daemon.ValidationSetResult)
   237  	c.Check(res, check.DeepEquals, []daemon.ValidationSetResult{
   238  		{
   239  			AccountID: s.dev1acct.AccountID(),
   240  			Name:      "baz",
   241  			Mode:      "monitor",
   242  			Sequence:  2,
   243  			Valid:     false,
   244  		},
   245  		{
   246  			AccountID: s.dev1acct.AccountID(),
   247  			Name:      "foo",
   248  			PinnedAt:  9,
   249  			Mode:      "enforce",
   250  			Sequence:  99,
   251  			Valid:     false,
   252  		},
   253  	})
   254  }
   255  
   256  func (s *apiValidationSetsSuite) TestGetValidationSetOne(c *check.C) {
   257  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   258  		return nil, &asserts.NotFoundError{
   259  			Type: assertType,
   260  		}
   261  	}
   262  
   263  	st := s.d.Overlord().State()
   264  	st.Lock()
   265  	as := s.mockAssert(c, "baz", "2")
   266  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as)
   267  	s.mockValidationSetsTracking(st)
   268  	st.Unlock()
   269  
   270  	req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/baz", s.dev1acct.AccountID()), nil)
   271  	c.Assert(err, check.IsNil)
   272  
   273  	rsp := s.syncReq(c, req, nil)
   274  	c.Assert(rsp.Status, check.Equals, 200)
   275  	res := rsp.Result.(daemon.ValidationSetResult)
   276  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   277  		AccountID: s.dev1acct.AccountID(),
   278  		Name:      "baz",
   279  		Mode:      "monitor",
   280  		Sequence:  2,
   281  		Valid:     false,
   282  	})
   283  }
   284  
   285  func (s *apiValidationSetsSuite) TestGetValidationSetPinned(c *check.C) {
   286  	q := url.Values{}
   287  	q.Set("sequence", "9")
   288  	req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/foo?%s", s.dev1acct.AccountID(), q.Encode()), nil)
   289  	c.Assert(err, check.IsNil)
   290  
   291  	st := s.d.Overlord().State()
   292  	st.Lock()
   293  	as := s.mockAssert(c, "foo", "9")
   294  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as)
   295  	s.mockValidationSetsTracking(st)
   296  	st.Unlock()
   297  	c.Assert(err, check.IsNil)
   298  
   299  	rsp := s.syncReq(c, req, nil)
   300  	c.Assert(rsp.Status, check.Equals, 200)
   301  	res := rsp.Result.(daemon.ValidationSetResult)
   302  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   303  		AccountID: s.dev1acct.AccountID(),
   304  		Name:      "foo",
   305  		PinnedAt:  9,
   306  		Mode:      "enforce",
   307  		Sequence:  99,
   308  		Valid:     false,
   309  	})
   310  }
   311  
   312  func (s *apiValidationSetsSuite) TestGetValidationSetNotFound(c *check.C) {
   313  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   314  		return nil, &asserts.NotFoundError{
   315  			Type: assertType,
   316  		}
   317  	}
   318  
   319  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   320  	c.Assert(err, check.IsNil)
   321  
   322  	st := s.d.Overlord().State()
   323  	st.Lock()
   324  	s.mockValidationSetsTracking(st)
   325  	st.Unlock()
   326  
   327  	rspe := s.errorReq(c, req, nil)
   328  	c.Assert(rspe.Status, check.Equals, 404)
   329  	c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found")
   330  	c.Check(rspe.Value, check.DeepEquals, map[string]interface{}{
   331  		"account-id": "foo",
   332  		"name":       "other",
   333  	})
   334  }
   335  
   336  var validationSetAssertion = []byte("type: validation-set\n" +
   337  	"format: 1\n" +
   338  	"authority-id: foo\n" +
   339  	"account-id: foo\n" +
   340  	"name: other\n" +
   341  	"sequence: 2\n" +
   342  	"revision: 5\n" +
   343  	"series: 16\n" +
   344  	"snaps:\n" +
   345  	"  -\n" +
   346  	"    id: yOqKhntON3vR7kwEbVPsILm7bUViPDzz\n" +
   347  	"    name: snap-b\n" +
   348  	"    presence: required\n" +
   349  	"    revision: 1\n" +
   350  	"timestamp: 2020-11-06T09:16:26Z\n" +
   351  	"sign-key-sha3-384: 7bbncP0c4RcufwReeiylCe0S7IMCn-tHLNSCgeOVmV3K-7_MzpAHgJDYeOjldefE\n\n" +
   352  	"AXNpZw==")
   353  
   354  func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemote(c *check.C) {
   355  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   356  		c.Assert(assertType, check.NotNil)
   357  		c.Assert(assertType.Name, check.Equals, "validation-set")
   358  		// no sequence number element, querying the latest
   359  		c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"})
   360  		c.Assert(sequence, check.Equals, 0)
   361  		as, err := asserts.Decode(validationSetAssertion)
   362  		c.Assert(err, check.IsNil)
   363  		// sanity
   364  		c.Assert(as.Type().Name, check.Equals, "validation-set")
   365  		return as, nil
   366  	}
   367  
   368  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error {
   369  		c.Assert(vsets, check.NotNil)
   370  		sort.Sort(byName(snaps))
   371  		c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{
   372  			{
   373  				SnapRef:  naming.NewSnapRef("snap-a", "snapaid"),
   374  				Revision: snap.R(2),
   375  			},
   376  			{
   377  				SnapRef:  naming.NewSnapRef("snap-b", "snapbid"),
   378  				Revision: snap.R(4),
   379  			},
   380  		})
   381  		// nil indicates successful validation
   382  		return nil
   383  	})
   384  	defer restore()
   385  
   386  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   387  	c.Assert(err, check.IsNil)
   388  
   389  	st := s.d.Overlord().State()
   390  	st.Lock()
   391  	s.mockValidationSetsTracking(st)
   392  
   393  	snapstate.Set(st, "snap-a", &snapstate.SnapState{
   394  		Active:   true,
   395  		Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(2), SnapID: "snapaid"}},
   396  		Current:  snap.R(2),
   397  	})
   398  	snapstate.Set(st, "snap-b", &snapstate.SnapState{
   399  		Active:   true,
   400  		Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(4), SnapID: "snapbid"}},
   401  		Current:  snap.R(4),
   402  	})
   403  
   404  	st.Unlock()
   405  
   406  	rsp := s.syncReq(c, req, nil)
   407  	c.Assert(rsp.Status, check.Equals, 200)
   408  	res := rsp.Result.(daemon.ValidationSetResult)
   409  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   410  		AccountID: "foo",
   411  		Name:      "other",
   412  		Sequence:  2,
   413  		Valid:     true,
   414  	})
   415  }
   416  
   417  func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteValidationFails(c *check.C) {
   418  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   419  		as, err := asserts.Decode(validationSetAssertion)
   420  		c.Assert(err, check.IsNil)
   421  		return as, nil
   422  	}
   423  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error {
   424  		return &snapasserts.ValidationSetsValidationError{}
   425  	})
   426  	defer restore()
   427  
   428  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   429  	c.Assert(err, check.IsNil)
   430  	rsp := s.syncReq(c, req, nil)
   431  	c.Assert(rsp.Status, check.Equals, 200)
   432  
   433  	res := rsp.Result.(daemon.ValidationSetResult)
   434  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   435  		AccountID: "foo",
   436  		Name:      "other",
   437  		Sequence:  2,
   438  		Valid:     false,
   439  	})
   440  }
   441  
   442  func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteRealValidation(c *check.C) {
   443  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   444  		as, err := asserts.Decode(validationSetAssertion)
   445  		c.Assert(err, check.IsNil)
   446  		return as, nil
   447  	}
   448  
   449  	st := s.d.Overlord().State()
   450  
   451  	for _, tc := range []struct {
   452  		revision                 snap.Revision
   453  		expectedValidationStatus bool
   454  	}{
   455  		// required at revision 1 per validationSetAssertion, so it's valid
   456  		{snap.R(1), true},
   457  		// but revision 2 is not valid
   458  		{snap.R(2), false},
   459  	} {
   460  		st.Lock()
   461  		snapstate.Set(st, "snap-b", &snapstate.SnapState{
   462  			Active:   true,
   463  			Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: tc.revision, SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}},
   464  			Current:  tc.revision,
   465  		})
   466  		st.Unlock()
   467  
   468  		req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   469  		c.Assert(err, check.IsNil)
   470  		rsp := s.syncReq(c, req, nil)
   471  		c.Assert(rsp.Status, check.Equals, 200)
   472  
   473  		res := rsp.Result.(daemon.ValidationSetResult)
   474  		c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   475  			AccountID: "foo",
   476  			Name:      "other",
   477  			Sequence:  2,
   478  			Valid:     tc.expectedValidationStatus,
   479  		})
   480  	}
   481  }
   482  
   483  func (s *apiValidationSetsSuite) TestGetValidationSetSpecificSequenceFromRemote(c *check.C) {
   484  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   485  		c.Assert(assertType, check.NotNil)
   486  		c.Assert(assertType.Name, check.Equals, "validation-set")
   487  		c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"})
   488  		c.Assert(sequence, check.Equals, 2)
   489  		as, err := asserts.Decode(validationSetAssertion)
   490  		c.Assert(err, check.IsNil)
   491  		return as, nil
   492  	}
   493  
   494  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error {
   495  		c.Assert(vsets, check.NotNil)
   496  		sort.Sort(byName(snaps))
   497  		c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{
   498  			{
   499  				SnapRef:  naming.NewSnapRef("snap-a", "snapaid"),
   500  				Revision: snap.R(33),
   501  			},
   502  		})
   503  		// nil indicates successful validation
   504  		return nil
   505  	})
   506  	defer restore()
   507  
   508  	q := url.Values{}
   509  	q.Set("sequence", "2")
   510  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other?"+q.Encode(), nil)
   511  	c.Assert(err, check.IsNil)
   512  
   513  	st := s.d.Overlord().State()
   514  	st.Lock()
   515  	s.mockValidationSetsTracking(st)
   516  
   517  	snapstate.Set(st, "snap-a", &snapstate.SnapState{
   518  		Active:   true,
   519  		Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(33), SnapID: "snapaid"}},
   520  		Current:  snap.R(33),
   521  	})
   522  
   523  	st.Unlock()
   524  
   525  	rsp := s.syncReq(c, req, nil)
   526  	c.Assert(rsp.Status, check.Equals, 200)
   527  	res := rsp.Result.(daemon.ValidationSetResult)
   528  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   529  		AccountID: "foo",
   530  		Name:      "other",
   531  		Sequence:  2,
   532  		Valid:     true,
   533  	})
   534  }
   535  
   536  func (s *apiValidationSetsSuite) TestGetValidationSetFromRemoteFallbackToLocalAssertion(c *check.C) {
   537  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   538  		// not found in the store
   539  		return nil, &asserts.NotFoundError{
   540  			Type: assertType,
   541  		}
   542  	}
   543  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error {
   544  		// nil indicates successful validation
   545  		return nil
   546  	})
   547  	defer restore()
   548  
   549  	st := s.d.Overlord().State()
   550  	st.Lock()
   551  	// assertion available in the local db (from snap ack)
   552  	vs := s.mockAssert(c, "bar", "2")
   553  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, vs)
   554  	st.Unlock()
   555  
   556  	q := url.Values{}
   557  	q.Set("sequence", "2")
   558  	req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/bar?%s", s.dev1acct.AccountID(), q.Encode()), nil)
   559  	c.Assert(err, check.IsNil)
   560  
   561  	rsp := s.syncReq(c, req, nil)
   562  	c.Assert(rsp.Status, check.Equals, 200)
   563  	res := rsp.Result.(daemon.ValidationSetResult)
   564  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   565  		AccountID: s.dev1acct.AccountID(),
   566  		Name:      "bar",
   567  		Sequence:  2,
   568  		Valid:     true,
   569  	})
   570  }
   571  
   572  func (s *apiValidationSetsSuite) TestGetValidationSetPinnedNotFound(c *check.C) {
   573  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   574  		return nil, &asserts.NotFoundError{
   575  			Type: assertType,
   576  		}
   577  	}
   578  
   579  	q := url.Values{}
   580  	q.Set("sequence", "333")
   581  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/bar?"+q.Encode(), nil)
   582  	c.Assert(err, check.IsNil)
   583  
   584  	st := s.d.Overlord().State()
   585  	st.Lock()
   586  	s.mockValidationSetsTracking(st)
   587  	st.Unlock()
   588  
   589  	rspe := s.errorReq(c, req, nil)
   590  	c.Assert(rspe.Status, check.Equals, 404)
   591  	c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found")
   592  	c.Check(rspe.Value, check.DeepEquals, map[string]interface{}{
   593  		"account-id": "foo",
   594  		"name":       "bar",
   595  		"sequence":   333,
   596  	})
   597  }
   598  
   599  func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedLocalOnly(c *check.C) {
   600  	restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) {
   601  		c.Assert(accountID, check.Equals, s.dev1acct.AccountID())
   602  		c.Assert(name, check.Equals, "bar")
   603  		c.Assert(sequence, check.Equals, 99)
   604  		c.Assert(pinned, check.Equals, true)
   605  		c.Assert(opts, check.NotNil)
   606  		c.Check(opts.AllowLocalFallback, check.Equals, true)
   607  
   608  		db := assertstate.DB(st)
   609  		headers, err := asserts.HeadersFromPrimaryKey(asserts.ValidationSetType, []string{release.Series, accountID, name, fmt.Sprintf("%d", sequence)})
   610  		c.Assert(err, check.IsNil)
   611  		// validation set assertion available locally
   612  		vs, err := db.Find(asserts.ValidationSetType, headers)
   613  		c.Assert(err, check.IsNil)
   614  		return vs.(*asserts.ValidationSet), true, nil
   615  	})
   616  	defer restore()
   617  
   618  	st := s.d.Overlord().State()
   619  
   620  	st.Lock()
   621  	vs := s.mockAssert(c, "bar", "99")
   622  	// add validation set assertion to the local db
   623  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, vs)
   624  	st.Unlock()
   625  
   626  	body := `{"action":"apply","mode":"monitor", "sequence":99}`
   627  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   628  	c.Assert(err, check.IsNil)
   629  
   630  	rsp := s.syncReq(c, req, nil)
   631  	c.Assert(rsp.Status, check.Equals, 200)
   632  
   633  	var tr assertstate.ValidationSetTracking
   634  
   635  	// verify tracking information
   636  	st.Lock()
   637  	err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr)
   638  	st.Unlock()
   639  	c.Assert(err, check.IsNil)
   640  	c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{
   641  		Mode:      assertstate.Monitor,
   642  		AccountID: s.dev1acct.AccountID(),
   643  		Name:      "bar",
   644  		PinnedAt:  99,
   645  		Current:   99,
   646  		LocalOnly: true,
   647  	})
   648  }
   649  
   650  func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedUnresolved(c *check.C) {
   651  	restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) {
   652  		c.Assert(accountID, check.Equals, s.dev1acct.AccountID())
   653  		c.Assert(name, check.Equals, "bar")
   654  		c.Assert(sequence, check.Equals, 99)
   655  		c.Assert(pinned, check.Equals, true)
   656  
   657  		snaps := []interface{}{map[string]interface{}{
   658  			"id":       "yOqKhntON3vR7kwEbVPsILm7bUViPDzz",
   659  			"name":     "snap-b",
   660  			"presence": "required",
   661  			"revision": "1",
   662  		}}
   663  		headers := map[string]interface{}{
   664  			"authority-id": s.dev1acct.AccountID(),
   665  			"account-id":   s.dev1acct.AccountID(),
   666  			"name":         "bar",
   667  			"series":       "16",
   668  			"sequence":     "99",
   669  			"revision":     "5",
   670  			"timestamp":    "2030-11-06T09:16:26Z",
   671  			"snaps":        snaps,
   672  		}
   673  		// validation set assertion coming from the store
   674  		vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "")
   675  		c.Assert(err, check.IsNil)
   676  		return vs.(*asserts.ValidationSet), false, nil
   677  	})
   678  	defer restore()
   679  
   680  	st := s.d.Overlord().State()
   681  
   682  	st.Lock()
   683  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   684  	st.Unlock()
   685  
   686  	body := `{"action":"apply","mode":"monitor", "sequence":99}`
   687  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   688  	c.Assert(err, check.IsNil)
   689  
   690  	rsp := s.syncReq(c, req, nil)
   691  	c.Assert(rsp.Status, check.Equals, 200)
   692  
   693  	var tr assertstate.ValidationSetTracking
   694  
   695  	// verify tracking information
   696  	st.Lock()
   697  	err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr)
   698  	st.Unlock()
   699  	c.Assert(err, check.IsNil)
   700  	c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{
   701  		Mode:      assertstate.Monitor,
   702  		AccountID: s.dev1acct.AccountID(),
   703  		Name:      "bar",
   704  		PinnedAt:  99,
   705  		Current:   99,
   706  	})
   707  }
   708  
   709  func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeUnpinnedRefreshed(c *check.C) {
   710  	snaps := []interface{}{map[string]interface{}{
   711  		"id":       "yOqKhntON3vR7kwEbVPsILm7bUViPDzz",
   712  		"name":     "snap-b",
   713  		"presence": "required",
   714  		"revision": "1",
   715  	}}
   716  
   717  	restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) {
   718  		c.Assert(accountID, check.Equals, s.dev1acct.AccountID())
   719  		c.Assert(name, check.Equals, "bar")
   720  		c.Assert(sequence, check.Equals, 0)
   721  		c.Assert(pinned, check.Equals, false)
   722  
   723  		// new sequence
   724  		headers := map[string]interface{}{
   725  			"authority-id": s.dev1acct.AccountID(),
   726  			"account-id":   s.dev1acct.AccountID(),
   727  			"name":         "bar",
   728  			"series":       "16",
   729  			"sequence":     "2",
   730  			"revision":     "1",
   731  			"timestamp":    "2030-11-06T09:16:26Z",
   732  			"snaps":        snaps,
   733  		}
   734  		// updated validation set assertion coming from the store
   735  		vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "")
   736  		c.Assert(err, check.IsNil)
   737  		return vs.(*asserts.ValidationSet), false, nil
   738  	})
   739  	defer restore()
   740  
   741  	st := s.d.Overlord().State()
   742  
   743  	st.Lock()
   744  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   745  	st.Unlock()
   746  
   747  	headers := map[string]interface{}{
   748  		"authority-id": s.dev1acct.AccountID(),
   749  		"account-id":   s.dev1acct.AccountID(),
   750  		"name":         "bar",
   751  		"series":       "16",
   752  		"sequence":     "1",
   753  		"revision":     "1",
   754  		"timestamp":    "2030-11-06T09:16:26Z",
   755  		"snaps":        snaps,
   756  	}
   757  	vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "")
   758  	c.Assert(err, check.IsNil)
   759  
   760  	st.Lock()
   761  	// add validation set assertion to the local db
   762  	c.Assert(assertstate.Add(st, vs), check.IsNil)
   763  	st.Unlock()
   764  
   765  	body := `{"action":"apply","mode":"monitor"}`
   766  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   767  	c.Assert(err, check.IsNil)
   768  
   769  	rsp := s.syncReq(c, req, nil)
   770  	c.Assert(rsp.Status, check.Equals, 200)
   771  
   772  	var tr assertstate.ValidationSetTracking
   773  
   774  	// verify tracking information
   775  	st.Lock()
   776  	err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr)
   777  	st.Unlock()
   778  	c.Assert(err, check.IsNil)
   779  	c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{
   780  		Mode:      assertstate.Monitor,
   781  		AccountID: s.dev1acct.AccountID(),
   782  		Name:      "bar",
   783  		Current:   2,
   784  	})
   785  }
   786  
   787  func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeError(c *check.C) {
   788  	restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) {
   789  		return nil, false, fmt.Errorf("boom")
   790  	})
   791  	defer restore()
   792  
   793  	body := `{"action":"apply","mode":"monitor"}`
   794  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   795  	c.Assert(err, check.IsNil)
   796  
   797  	rspe := s.errorReq(c, req, nil)
   798  	c.Assert(rspe.Status, check.Equals, 400)
   799  	c.Check(rspe.Message, check.Equals, fmt.Sprintf(`cannot get validation set assertion for %s/bar: boom`, s.dev1acct.AccountID()))
   800  }
   801  
   802  func (s *apiValidationSetsSuite) TestForgetValidationSet(c *check.C) {
   803  	st := s.d.Overlord().State()
   804  
   805  	for i, sequence := range []int{0, 9} {
   806  		st.Lock()
   807  		s.mockValidationSetsTracking(st)
   808  		st.Unlock()
   809  
   810  		var body string
   811  		if sequence != 0 {
   812  			body = fmt.Sprintf(`{"action":"forget", "sequence":%d}`, sequence)
   813  		} else {
   814  			body = `{"action":"forget"}`
   815  		}
   816  
   817  		var tr assertstate.ValidationSetTracking
   818  
   819  		st.Lock()
   820  		// sanity, it exists before removing
   821  		err := assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr)
   822  		st.Unlock()
   823  		c.Assert(err, check.IsNil)
   824  		c.Check(tr.AccountID, check.Equals, s.dev1acct.AccountID())
   825  		c.Check(tr.Name, check.Equals, "foo")
   826  
   827  		req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body))
   828  		c.Assert(err, check.IsNil)
   829  		rsp := s.syncReq(c, req, nil)
   830  		c.Assert(rsp.Status, check.Equals, 200, check.Commentf("case #%d", i))
   831  
   832  		// after forget it's removed
   833  		st.Lock()
   834  		err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr)
   835  		st.Unlock()
   836  		c.Assert(err, check.Equals, state.ErrNoState)
   837  
   838  		// and forget again fails
   839  		req, err = http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body))
   840  		c.Assert(err, check.IsNil)
   841  		rspe := s.errorReq(c, req, nil)
   842  		c.Assert(rspe.Status, check.Equals, 404, check.Commentf("case #%d", i))
   843  	}
   844  }
   845  
   846  func (s *apiValidationSetsSuite) TestApplyValidationSetsErrors(c *check.C) {
   847  	st := s.d.Overlord().State()
   848  	st.Lock()
   849  	s.mockValidationSetsTracking(st)
   850  	st.Unlock()
   851  
   852  	for i, tc := range []struct {
   853  		validationSet string
   854  		mode          string
   855  		// sequence is normally an int, use string for passing invalid ones.
   856  		sequence string
   857  		message  string
   858  		status   int
   859  	}{
   860  		{
   861  			validationSet: "0/zzz",
   862  			mode:          "monitor",
   863  			message:       `invalid account ID "0"`,
   864  			status:        400,
   865  		},
   866  		{
   867  			validationSet: "Xfoo/bar",
   868  			mode:          "monitor",
   869  			message:       `invalid account ID "Xfoo"`,
   870  			status:        400,
   871  		},
   872  		{
   873  			validationSet: "foo/Xabc",
   874  			mode:          "monitor",
   875  			message:       `invalid name "Xabc"`,
   876  			status:        400,
   877  		},
   878  		{
   879  			validationSet: "foo/bar",
   880  			sequence:      "x",
   881  			message:       "cannot decode request body into validation set action: invalid character 'x' looking for beginning of value",
   882  			status:        400,
   883  		},
   884  		{
   885  			validationSet: "foo/bar",
   886  			mode:          "bad",
   887  			message:       `invalid mode "bad"`,
   888  			status:        400,
   889  		},
   890  		// XXX: enable when enforcing is implemented.
   891  		{
   892  			validationSet: "foo/bar",
   893  			mode:          "enforce",
   894  			message:       `invalid mode "enforce"`,
   895  			status:        400,
   896  		},
   897  		{
   898  			validationSet: "foo/bar",
   899  			sequence:      "-1",
   900  			mode:          "monitor",
   901  			message:       `invalid sequence argument: -1`,
   902  			status:        400,
   903  		},
   904  	} {
   905  		var body string
   906  		if tc.sequence != "" {
   907  			body = fmt.Sprintf(`{"action":"apply","mode":"%s", "sequence":%s}`, tc.mode, tc.sequence)
   908  		} else {
   909  			body = fmt.Sprintf(`{"action":"apply","mode":"%s"}`, tc.mode)
   910  		}
   911  		req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s", tc.validationSet), strings.NewReader(body))
   912  		c.Assert(err, check.IsNil)
   913  		rspe := s.errorReq(c, req, nil)
   914  		c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i))
   915  		c.Check(rspe.Message, check.Matches, tc.message)
   916  	}
   917  }
   918  
   919  func (s *apiValidationSetsSuite) TestApplyValidationSetUnsupportedAction(c *check.C) {
   920  	body := `{"action":"baz","mode":"monitor"}`
   921  
   922  	req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body))
   923  	c.Assert(err, check.IsNil)
   924  
   925  	rspe := s.errorReq(c, req, nil)
   926  	c.Check(rspe.Status, check.Equals, 400)
   927  	c.Check(rspe.Message, check.Matches, `unsupported action "baz"`)
   928  }