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