gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/constraint_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2022 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 asserts_test
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  
    26  	. "gopkg.in/check.v1"
    27  	"gopkg.in/yaml.v2"
    28  
    29  	"gitee.com/mysnapcore/mysnapd/asserts"
    30  	"gitee.com/mysnapcore/mysnapd/metautil"
    31  	"gitee.com/mysnapcore/mysnapd/snap"
    32  	"gitee.com/mysnapcore/mysnapd/testutil"
    33  )
    34  
    35  type attrMatcherSuite struct {
    36  	testutil.BaseTest
    37  }
    38  
    39  var _ = Suite(&attrMatcherSuite{})
    40  
    41  func vals(yml string) map[string]interface{} {
    42  	var vs map[string]interface{}
    43  	err := yaml.Unmarshal([]byte(yml), &vs)
    44  	if err != nil {
    45  		panic(err)
    46  	}
    47  	v, err := metautil.NormalizeValue(vs)
    48  	if err != nil {
    49  		panic(err)
    50  	}
    51  	return v.(map[string]interface{})
    52  }
    53  
    54  func (s *attrMatcherSuite) SetUpTest(c *C) {
    55  	s.BaseTest.SetUpTest(c)
    56  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    57  }
    58  
    59  func (s *attrMatcherSuite) TestSimple(c *C) {
    60  	m, err := asserts.ParseHeaders([]byte(`attrs:
    61    foo: FOO
    62    bar: BAR`))
    63  	c.Assert(err, IsNil)
    64  
    65  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
    66  	c.Assert(err, IsNil)
    67  
    68  	values := map[string]interface{}{
    69  		"foo": "FOO",
    70  		"bar": "BAR",
    71  		"baz": "BAZ",
    72  	}
    73  	err = domatch(values, nil)
    74  	c.Check(err, IsNil)
    75  
    76  	values = map[string]interface{}{
    77  		"foo": "FOO",
    78  		"bar": "BAZ",
    79  		"baz": "BAZ",
    80  	}
    81  	err = domatch(values, nil)
    82  	c.Check(err, ErrorMatches, `field "bar" value "BAZ" does not match \^\(BAR\)\$`)
    83  
    84  	values = map[string]interface{}{
    85  		"foo": "FOO",
    86  		"baz": "BAZ",
    87  	}
    88  	err = domatch(values, nil)
    89  	c.Check(err, ErrorMatches, `field "bar" has constraints but is unset`)
    90  }
    91  
    92  func (s *attrMatcherSuite) TestSimpleAnchorsVsRegexpAlt(c *C) {
    93  	m, err := asserts.ParseHeaders([]byte(`attrs:
    94    bar: BAR|BAZ`))
    95  	c.Assert(err, IsNil)
    96  
    97  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
    98  	c.Assert(err, IsNil)
    99  
   100  	values := map[string]interface{}{
   101  		"bar": "BAR",
   102  	}
   103  	err = domatch(values, nil)
   104  	c.Check(err, IsNil)
   105  
   106  	values = map[string]interface{}{
   107  		"bar": "BARR",
   108  	}
   109  	err = domatch(values, nil)
   110  	c.Check(err, ErrorMatches, `field "bar" value "BARR" does not match \^\(BAR|BAZ\)\$`)
   111  
   112  	values = map[string]interface{}{
   113  		"bar": "BBAZ",
   114  	}
   115  	err = domatch(values, nil)
   116  	c.Check(err, ErrorMatches, `field "bar" value "BAZZ" does not match \^\(BAR|BAZ\)\$`)
   117  
   118  	values = map[string]interface{}{
   119  		"bar": "BABAZ",
   120  	}
   121  	err = domatch(values, nil)
   122  	c.Check(err, ErrorMatches, `field "bar" value "BABAZ" does not match \^\(BAR|BAZ\)\$`)
   123  
   124  	values = map[string]interface{}{
   125  		"bar": "BARAZ",
   126  	}
   127  	err = domatch(values, nil)
   128  	c.Check(err, ErrorMatches, `field "bar" value "BARAZ" does not match \^\(BAR|BAZ\)\$`)
   129  }
   130  
   131  func (s *attrMatcherSuite) TestNested(c *C) {
   132  	m, err := asserts.ParseHeaders([]byte(`attrs:
   133    foo: FOO
   134    bar:
   135      bar1: BAR1
   136      bar2: BAR2`))
   137  	c.Assert(err, IsNil)
   138  
   139  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   140  	c.Assert(err, IsNil)
   141  
   142  	err = domatch(vals(`
   143  foo: FOO
   144  bar:
   145    bar1: BAR1
   146    bar2: BAR2
   147    bar3: BAR3
   148  baz: BAZ
   149  `), nil)
   150  	c.Check(err, IsNil)
   151  
   152  	err = domatch(vals(`
   153  foo: FOO
   154  bar: BAZ
   155  baz: BAZ
   156  `), nil)
   157  	c.Check(err, ErrorMatches, `field "bar" must be a map`)
   158  
   159  	err = domatch(vals(`
   160  foo: FOO
   161  bar:
   162    bar1: BAR1
   163    bar2: BAR22
   164    bar3: BAR3
   165  baz: BAZ
   166  `), nil)
   167  	c.Check(err, ErrorMatches, `field "bar\.bar2" value "BAR22" does not match \^\(BAR2\)\$`)
   168  
   169  	err = domatch(vals(`
   170  foo: FOO
   171  bar:
   172    bar1: BAR1
   173    bar2:
   174      bar22: true
   175    bar3: BAR3
   176  baz: BAZ
   177  `), nil)
   178  	c.Check(err, ErrorMatches, `field "bar\.bar2" must be a scalar or list`)
   179  }
   180  
   181  func (s *attrMatcherSuite) TestAlternative(c *C) {
   182  	m, err := asserts.ParseHeaders([]byte(`attrs:
   183    -
   184      foo: FOO
   185      bar: BAR
   186    -
   187      foo: FOO
   188      bar: BAZ`))
   189  	c.Assert(err, IsNil)
   190  
   191  	domatch, err := asserts.CompileAttrMatcher(m["attrs"], nil)
   192  	c.Assert(err, IsNil)
   193  
   194  	values := map[string]interface{}{
   195  		"foo": "FOO",
   196  		"bar": "BAR",
   197  		"baz": "BAZ",
   198  	}
   199  	err = domatch(values, nil)
   200  	c.Check(err, IsNil)
   201  
   202  	values = map[string]interface{}{
   203  		"foo": "FOO",
   204  		"bar": "BAZ",
   205  		"baz": "BAZ",
   206  	}
   207  	err = domatch(values, nil)
   208  	c.Check(err, IsNil)
   209  
   210  	values = map[string]interface{}{
   211  		"foo": "FOO",
   212  		"bar": "BARR",
   213  		"baz": "BAR",
   214  	}
   215  	err = domatch(values, nil)
   216  	c.Check(err, ErrorMatches, `no alternative matches: field "bar" value "BARR" does not match \^\(BAR\)\$`)
   217  }
   218  
   219  func (s *attrMatcherSuite) TestNestedAlternative(c *C) {
   220  	m, err := asserts.ParseHeaders([]byte(`attrs:
   221    foo: FOO
   222    bar:
   223      bar1: BAR1
   224      bar2:
   225        - BAR2
   226        - BAR22`))
   227  	c.Assert(err, IsNil)
   228  
   229  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   230  	c.Assert(err, IsNil)
   231  
   232  	err = domatch(vals(`
   233  foo: FOO
   234  bar:
   235    bar1: BAR1
   236    bar2: BAR2
   237  `), nil)
   238  	c.Check(err, IsNil)
   239  
   240  	err = domatch(vals(`
   241  foo: FOO
   242  bar:
   243    bar1: BAR1
   244    bar2: BAR22
   245  `), nil)
   246  	c.Check(err, IsNil)
   247  
   248  	err = domatch(vals(`
   249  foo: FOO
   250  bar:
   251    bar1: BAR1
   252    bar2: BAR3
   253  `), nil)
   254  	c.Check(err, ErrorMatches, `no alternative for field "bar\.bar2" matches: field "bar\.bar2" value "BAR3" does not match \^\(BAR2\)\$`)
   255  }
   256  
   257  func (s *attrMatcherSuite) TestAlternativeMatchingStringList(c *C) {
   258  	toMatch := vals(`
   259  write:
   260   - /var/tmp
   261   - /var/lib/snapd/snapshots
   262  `)
   263  	m, err := asserts.ParseHeaders([]byte(`attrs:
   264    write: /var/(tmp|lib/snapd/snapshots)`))
   265  	c.Assert(err, IsNil)
   266  
   267  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   268  	c.Assert(err, IsNil)
   269  
   270  	err = domatch(toMatch, nil)
   271  	c.Check(err, IsNil)
   272  
   273  	m, err = asserts.ParseHeaders([]byte(`attrs:
   274    write:
   275      - /var/tmp
   276      - /var/lib/snapd/snapshots`))
   277  	c.Assert(err, IsNil)
   278  
   279  	domatchLst, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   280  	c.Assert(err, IsNil)
   281  
   282  	err = domatchLst(toMatch, nil)
   283  	c.Check(err, IsNil)
   284  }
   285  
   286  func (s *attrMatcherSuite) TestAlternativeMatchingComplex(c *C) {
   287  	toMatch := vals(`
   288  mnt: [{what: "/dev/x*", where: "/foo/*", options: ["rw", "nodev"]}, {what: "/bar/*", where: "/baz/*", options: ["rw", "bind"]}]
   289  `)
   290  
   291  	m, err := asserts.ParseHeaders([]byte(`attrs:
   292    mnt:
   293      -
   294        what: /(bar/|dev/x)\*
   295        where: /(foo|baz)/\*
   296        options: rw|bind|nodev`))
   297  	c.Assert(err, IsNil)
   298  
   299  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   300  	c.Assert(err, IsNil)
   301  
   302  	err = domatch(toMatch, nil)
   303  	c.Check(err, IsNil)
   304  
   305  	m, err = asserts.ParseHeaders([]byte(`attrs:
   306    mnt:
   307      -
   308        what: /dev/x\*
   309        where: /foo/\*
   310        options:
   311          - nodev
   312          - rw
   313      -
   314        what: /bar/\*
   315        where: /baz/\*
   316        options:
   317          - rw
   318          - bind`))
   319  	c.Assert(err, IsNil)
   320  
   321  	domatchExtensive, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   322  	c.Assert(err, IsNil)
   323  
   324  	err = domatchExtensive(toMatch, nil)
   325  	c.Check(err, IsNil)
   326  
   327  	// not matching case
   328  	m, err = asserts.ParseHeaders([]byte(`attrs:
   329    mnt:
   330      -
   331        what: /dev/x\*
   332        where: /foo/\*
   333        options:
   334          - rw
   335      -
   336        what: /bar/\*
   337        where: /baz/\*
   338        options:
   339          - rw
   340          - bind`))
   341  	c.Assert(err, IsNil)
   342  
   343  	domatchExtensiveNoMatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   344  	c.Assert(err, IsNil)
   345  
   346  	err = domatchExtensiveNoMatch(toMatch, nil)
   347  	c.Check(err, ErrorMatches, `no alternative for field "mnt\.0" matches: no alternative for field "mnt\.0.options\.1" matches:.*`)
   348  }
   349  
   350  func (s *attrMatcherSuite) TestOtherScalars(c *C) {
   351  	m, err := asserts.ParseHeaders([]byte(`attrs:
   352    foo: 1
   353    bar: true`))
   354  	c.Assert(err, IsNil)
   355  
   356  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   357  	c.Assert(err, IsNil)
   358  
   359  	err = domatch(vals(`
   360  foo: 1
   361  bar: true
   362  `), nil)
   363  	c.Check(err, IsNil)
   364  
   365  	values := map[string]interface{}{
   366  		"foo": int64(1),
   367  		"bar": true,
   368  	}
   369  	err = domatch(values, nil)
   370  	c.Check(err, IsNil)
   371  }
   372  
   373  func (s *attrMatcherSuite) TestCompileErrors(c *C) {
   374  	_, err := asserts.CompileAttrMatcher(1, nil)
   375  	c.Check(err, ErrorMatches, `top constraint must be a key-value map, regexp or a list of alternative constraints: 1`)
   376  
   377  	_, err = asserts.CompileAttrMatcher(map[string]interface{}{
   378  		"foo": 1,
   379  	}, nil)
   380  	c.Check(err, ErrorMatches, `constraint "foo" must be a key-value map, regexp or a list of alternative constraints: 1`)
   381  
   382  	_, err = asserts.CompileAttrMatcher(map[string]interface{}{
   383  		"foo": "[",
   384  	}, nil)
   385  	c.Check(err, ErrorMatches, `cannot compile "foo" constraint "\[": error parsing regexp:.*`)
   386  
   387  	_, err = asserts.CompileAttrMatcher(map[string]interface{}{
   388  		"foo": []interface{}{"foo", "["},
   389  	}, nil)
   390  	c.Check(err, ErrorMatches, `cannot compile "foo/alt#2/" constraint "\[": error parsing regexp:.*`)
   391  
   392  	_, err = asserts.CompileAttrMatcher(map[string]interface{}{
   393  		"foo": []interface{}{"foo", []interface{}{"bar", "baz"}},
   394  	}, nil)
   395  	c.Check(err, ErrorMatches, `cannot nest alternative constraints directly at "foo/alt#2/"`)
   396  
   397  	_, err = asserts.CompileAttrMatcher("FOO", nil)
   398  	c.Check(err, ErrorMatches, `first level of non alternative constraints must be a set of key-value contraints`)
   399  
   400  	_, err = asserts.CompileAttrMatcher([]interface{}{"FOO"}, nil)
   401  	c.Check(err, ErrorMatches, `first level of non alternative constraints must be a set of key-value contraints`)
   402  
   403  	_, err = asserts.CompileAttrMatcher(map[string]interface{}{
   404  		"foo": "$FOO()",
   405  	}, nil)
   406  	c.Check(err, ErrorMatches, `cannot compile "foo" constraint "\$FOO\(\)": no \$OP\(\) constraints supported`)
   407  
   408  	wrongDollarConstraints := []string{
   409  		"$",
   410  		"$FOO(a)",
   411  		"$SLOT",
   412  		"$SLOT()",
   413  		"$SLOT(x,y)",
   414  		"$SLOT(x,y,z)",
   415  	}
   416  
   417  	for _, wrong := range wrongDollarConstraints {
   418  		_, err := asserts.CompileAttrMatcher(map[string]interface{}{
   419  			"foo": wrong,
   420  		}, []string{"SLOT", "OP"})
   421  		if wrong != "$SLOT(x,y)" {
   422  			c.Check(err, ErrorMatches, fmt.Sprintf(`cannot compile "foo" constraint "%s": not a valid \$SLOT\(\)/\$OP\(\) constraint`, regexp.QuoteMeta(wrong)))
   423  		} else {
   424  			c.Check(err, ErrorMatches, fmt.Sprintf(`cannot compile "foo" constraint "%s": \$SLOT\(\) constraint expects 1 argument`, regexp.QuoteMeta(wrong)))
   425  		}
   426  
   427  	}
   428  }
   429  
   430  func (s *attrMatcherSuite) TestMatchingListsSimple(c *C) {
   431  	m, err := asserts.ParseHeaders([]byte(`attrs:
   432    foo: /foo/.*`))
   433  	c.Assert(err, IsNil)
   434  
   435  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   436  	c.Assert(err, IsNil)
   437  
   438  	err = domatch(vals(`
   439  foo: ["/foo/x", "/foo/y"]
   440  `), nil)
   441  	c.Check(err, IsNil)
   442  
   443  	err = domatch(vals(`
   444  foo: ["/foo/x", "/foo"]
   445  `), nil)
   446  	c.Check(err, ErrorMatches, `field "foo\.1" value "/foo" does not match \^\(/foo/\.\*\)\$`)
   447  }
   448  
   449  func (s *attrMatcherSuite) TestMissingCheck(c *C) {
   450  	m, err := asserts.ParseHeaders([]byte(`attrs:
   451    foo: $MISSING`))
   452  	c.Assert(err, IsNil)
   453  
   454  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   455  	c.Assert(err, IsNil)
   456  
   457  	err = domatch(vals(`
   458  bar: baz
   459  `), nil)
   460  	c.Check(err, IsNil)
   461  
   462  	err = domatch(vals(`
   463  foo: ["x"]
   464  `), nil)
   465  	c.Check(err, ErrorMatches, `field "foo" is constrained to be missing but is set`)
   466  }
   467  
   468  func (s *attrMatcherSuite) TestEvalCheck(c *C) {
   469  	// TODO: consider rewriting once we have $WITHIN
   470  	m, err := asserts.ParseHeaders([]byte(`attrs:
   471    foo: $SLOT(foo)
   472    bar: $PLUG(bar.baz)`))
   473  	c.Assert(err, IsNil)
   474  
   475  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), []string{"SLOT", "PLUG"})
   476  	c.Assert(err, IsNil)
   477  
   478  	err = domatch(vals(`
   479  foo: foo
   480  bar: bar
   481  `), nil)
   482  	c.Check(err, ErrorMatches, `field "(foo|bar)" cannot be matched without context`)
   483  
   484  	calls := make(map[[2]string]bool)
   485  	comp1 := func(op string, arg string) (interface{}, error) {
   486  		calls[[2]string{op, arg}] = true
   487  		return arg, nil
   488  	}
   489  
   490  	err = domatch(vals(`
   491  foo: foo
   492  bar: bar.baz
   493  `), testEvalAttr{comp1})
   494  	c.Check(err, IsNil)
   495  
   496  	c.Check(calls, DeepEquals, map[[2]string]bool{
   497  		{"slot", "foo"}:     true,
   498  		{"plug", "bar.baz"}: true,
   499  	})
   500  
   501  	comp2 := func(op string, arg string) (interface{}, error) {
   502  		if op == "plug" {
   503  			return nil, fmt.Errorf("boom")
   504  		}
   505  		return arg, nil
   506  	}
   507  
   508  	err = domatch(vals(`
   509  foo: foo
   510  bar: bar.baz
   511  `), testEvalAttr{comp2})
   512  	c.Check(err, ErrorMatches, `field "bar" constraint \$PLUG\(bar\.baz\) cannot be evaluated: boom`)
   513  
   514  	comp3 := func(op string, arg string) (interface{}, error) {
   515  		if op == "slot" {
   516  			return "other-value", nil
   517  		}
   518  		return arg, nil
   519  	}
   520  
   521  	err = domatch(vals(`
   522  foo: foo
   523  bar: bar.baz
   524  `), testEvalAttr{comp3})
   525  	c.Check(err, ErrorMatches, `field "foo" does not match \$SLOT\(foo\): foo != other-value`)
   526  }
   527  
   528  func (s *attrMatcherSuite) TestMatchingListsMap(c *C) {
   529  	m, err := asserts.ParseHeaders([]byte(`attrs:
   530    foo:
   531      p: /foo/.*`))
   532  	c.Assert(err, IsNil)
   533  
   534  	domatch, err := asserts.CompileAttrMatcher(m["attrs"].(map[string]interface{}), nil)
   535  	c.Assert(err, IsNil)
   536  
   537  	err = domatch(vals(`
   538  foo: [{p: "/foo/x"}, {p: "/foo/y"}]
   539  `), nil)
   540  	c.Check(err, IsNil)
   541  
   542  	err = domatch(vals(`
   543  foo: [{p: "zzz"}, {p: "/foo/y"}]
   544  `), nil)
   545  	c.Check(err, ErrorMatches, `field "foo\.0\.p" value "zzz" does not match \^\(/foo/\.\*\)\$`)
   546  }
   547  
   548  type deviceScopeConstraintSuite struct {
   549  	testutil.BaseTest
   550  }
   551  
   552  var _ = Suite(&deviceScopeConstraintSuite{})
   553  
   554  func (s *deviceScopeConstraintSuite) TestCompile(c *C) {
   555  	tests := []struct {
   556  		m   map[string]interface{}
   557  		exp *asserts.DeviceScopeConstraint
   558  		err string
   559  	}{
   560  		{m: nil, exp: nil},
   561  		{m: map[string]interface{}{"on-store": []interface{}{"foo", "bar"}}, exp: &asserts.DeviceScopeConstraint{Store: []string{"foo", "bar"}}},
   562  		{m: map[string]interface{}{"on-brand": []interface{}{"foo", "bar"}}, exp: &asserts.DeviceScopeConstraint{Brand: []string{"foo", "bar"}}},
   563  		{m: map[string]interface{}{"on-model": []interface{}{"foo/model1", "bar/model-2"}}, exp: &asserts.DeviceScopeConstraint{Model: []string{"foo/model1", "bar/model-2"}}},
   564  		{
   565  			m: map[string]interface{}{
   566  				"on-brand": []interface{}{"foo", "bar"},
   567  				"on-model": []interface{}{"foo/model1", "bar/model-2"},
   568  				"on-store": []interface{}{"foo", "bar"},
   569  			},
   570  			exp: &asserts.DeviceScopeConstraint{
   571  				Store: []string{"foo", "bar"},
   572  				Brand: []string{"foo", "bar"},
   573  				Model: []string{"foo/model1", "bar/model-2"},
   574  			},
   575  		},
   576  		{m: map[string]interface{}{"on-store": ""}, err: `on-store in constraint must be a list of strings`},
   577  		{m: map[string]interface{}{"on-brand": "foo"}, err: `on-brand in constraint must be a list of strings`},
   578  		{m: map[string]interface{}{"on-model": map[string]interface{}{"brand": "x"}}, err: `on-model in constraint must be a list of strings`},
   579  	}
   580  
   581  	for _, t := range tests {
   582  		dsc, err := asserts.CompileDeviceScopeConstraint(t.m, "constraint")
   583  		if t.err == "" {
   584  			c.Check(err, IsNil)
   585  			c.Check(dsc, DeepEquals, t.exp)
   586  		} else {
   587  			c.Check(err, ErrorMatches, t.err)
   588  			c.Check(dsc, IsNil)
   589  		}
   590  	}
   591  }
   592  
   593  func (s *deviceScopeConstraintSuite) TestValidOnStoreBrandModel(c *C) {
   594  	tests := []struct {
   595  		constr string
   596  		value  string
   597  		valid  bool
   598  	}{
   599  		{"on-store", "", false},
   600  		{"on-store", "foo", true},
   601  		{"on-store", "F_o-O88", true},
   602  		{"on-store", "foo!", false},
   603  		{"on-store", "foo.", false},
   604  		{"on-store", "foo/", false},
   605  		{"on-brand", "", false},
   606  		// custom set brands (length 2-28)
   607  		{"on-brand", "dwell", true},
   608  		{"on-brand", "Dwell", false},
   609  		{"on-brand", "dwell-88", true},
   610  		{"on-brand", "dwell_88", false},
   611  		{"on-brand", "dwell.88", false},
   612  		{"on-brand", "dwell:88", false},
   613  		{"on-brand", "dwell!88", false},
   614  		{"on-brand", "a", false},
   615  		{"on-brand", "ab", true},
   616  		{"on-brand", "0123456789012345678901234567", true},
   617  		// snappy id brands (fixed length 32)
   618  		{"on-brand", "01234567890123456789012345678", false},
   619  		{"on-brand", "012345678901234567890123456789", false},
   620  		{"on-brand", "0123456789012345678901234567890", false},
   621  		{"on-brand", "01234567890123456789012345678901", true},
   622  		{"on-brand", "abcdefghijklmnopqrstuvwxyz678901", true},
   623  		{"on-brand", "ABCDEFGHIJKLMNOPQRSTUVWCYZ678901", true},
   624  		{"on-brand", "ABCDEFGHIJKLMNOPQRSTUVWCYZ678901X", false},
   625  		{"on-brand", "ABCDEFGHIJKLMNOPQ!STUVWCYZ678901", false},
   626  		{"on-brand", "ABCDEFGHIJKLMNOPQ_STUVWCYZ678901", false},
   627  		{"on-brand", "ABCDEFGHIJKLMNOPQ-STUVWCYZ678901", false},
   628  		{"on-model", "", false},
   629  		{"on-model", "/", false},
   630  		{"on-model", "dwell/dwell1", true},
   631  		{"on-model", "dwell", false},
   632  		{"on-model", "dwell/", false},
   633  		{"on-model", "dwell//dwell1", false},
   634  		{"on-model", "dwell/-dwell1", false},
   635  		{"on-model", "dwell/dwell1-", false},
   636  		{"on-model", "dwell/dwell1-23", true},
   637  		{"on-model", "dwell/dwell1!", false},
   638  		{"on-model", "dwell/dwe_ll1", false},
   639  		{"on-model", "dwell/dwe.ll1", false},
   640  	}
   641  
   642  	check := func(constr, value string, valid bool) {
   643  		cMap := map[string]interface{}{
   644  			constr: []interface{}{value},
   645  		}
   646  
   647  		_, err := asserts.CompileDeviceScopeConstraint(cMap, "constraint")
   648  		if valid {
   649  			c.Check(err, IsNil, Commentf("%v", cMap))
   650  		} else {
   651  			c.Check(err, ErrorMatches, fmt.Sprintf(`%s in constraint contains an invalid element: %q`, constr, value), Commentf("%v", cMap))
   652  		}
   653  	}
   654  
   655  	for _, t := range tests {
   656  		check(t.constr, t.value, t.valid)
   657  
   658  		if t.constr == "on-brand" {
   659  			// reuse and double check all brands also in the context of on-model!
   660  
   661  			check("on-model", t.value+"/foo", t.valid)
   662  		}
   663  	}
   664  }
   665  
   666  func (s *deviceScopeConstraintSuite) TestCheck(c *C) {
   667  	a, err := asserts.Decode([]byte(`type: model
   668  authority-id: my-brand
   669  series: 16
   670  brand-id: my-brand
   671  model: my-model1
   672  store: store1
   673  architecture: armhf
   674  kernel: krnl
   675  gadget: gadget
   676  timestamp: 2018-09-12T12:00:00Z
   677  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   678  
   679  AXNpZw==`))
   680  	c.Assert(err, IsNil)
   681  	myModel1 := a.(*asserts.Model)
   682  
   683  	a, err = asserts.Decode([]byte(`type: model
   684  authority-id: my-brand-subbrand
   685  series: 16
   686  brand-id: my-brand-subbrand
   687  model: my-model2
   688  store: store2
   689  architecture: armhf
   690  kernel: krnl
   691  gadget: gadget
   692  timestamp: 2018-09-12T12:00:00Z
   693  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   694  
   695  AXNpZw==`))
   696  	c.Assert(err, IsNil)
   697  	myModel2 := a.(*asserts.Model)
   698  
   699  	a, err = asserts.Decode([]byte(`type: model
   700  authority-id: my-brand
   701  series: 16
   702  brand-id: my-brand
   703  model: my-model3
   704  store: substore1
   705  architecture: armhf
   706  kernel: krnl
   707  gadget: gadget
   708  timestamp: 2018-09-12T12:00:00Z
   709  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   710  
   711  AXNpZw==`))
   712  	c.Assert(err, IsNil)
   713  	myModel3 := a.(*asserts.Model)
   714  
   715  	a, err = asserts.Decode([]byte(`type: store
   716  store: substore1
   717  authority-id: canonical
   718  operator-id: canonical
   719  friendly-stores:
   720    - a-store
   721    - store1
   722    - store2
   723  timestamp: 2018-09-12T12:00:00Z
   724  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   725  
   726  AXNpZw==`))
   727  	c.Assert(err, IsNil)
   728  	substore1 := a.(*asserts.Store)
   729  
   730  	tests := []struct {
   731  		m                 map[string]interface{}
   732  		model             *asserts.Model
   733  		store             *asserts.Store
   734  		useFriendlyStores bool
   735  		err               string
   736  	}{
   737  		{m: map[string]interface{}{"on-store": []interface{}{"store1"}}, model: myModel1},
   738  		{m: map[string]interface{}{"on-store": []interface{}{"a-store", "store1"}}, model: myModel1},
   739  		{m: map[string]interface{}{"on-store": []interface{}{"store1"}}, model: myModel2, err: "on-store mismatch"},
   740  		{m: map[string]interface{}{"on-store": []interface{}{"store1"}}, model: myModel3, store: substore1, useFriendlyStores: true},
   741  		{m: map[string]interface{}{"on-store": []interface{}{"store1"}}, model: myModel3, store: substore1, useFriendlyStores: false, err: "on-store mismatch"},
   742  		{m: map[string]interface{}{"on-store": []interface{}{"store1"}}, model: nil, err: `cannot match on-store/on-brand/on-model without model`},
   743  		{m: map[string]interface{}{"on-store": []interface{}{"store1"}}, model: myModel2, store: substore1, err: `store assertion and model store must match`, useFriendlyStores: true},
   744  		{m: map[string]interface{}{"on-store": []interface{}{"other-store"}}, model: myModel3, store: substore1, err: "on-store mismatch"},
   745  		{m: map[string]interface{}{"on-brand": []interface{}{"my-brand"}}, model: myModel1},
   746  		{m: map[string]interface{}{"on-brand": []interface{}{"my-brand", "my-brand-subbrand"}}, model: myModel2},
   747  		{m: map[string]interface{}{"on-brand": []interface{}{"other-brand"}}, model: myModel2, err: "on-brand mismatch"},
   748  		{m: map[string]interface{}{"on-model": []interface{}{"my-brand/my-model1"}}, model: myModel1},
   749  		{m: map[string]interface{}{"on-model": []interface{}{"my-brand/other-model"}}, model: myModel1, err: "on-model mismatch"},
   750  		{m: map[string]interface{}{"on-model": []interface{}{"my-brand/my-model", "my-brand-subbrand/my-model2", "other-brand/other-model"}}, model: myModel2},
   751  		{
   752  			m: map[string]interface{}{
   753  				"on-store": []interface{}{"store2"},
   754  				"on-brand": []interface{}{"my-brand", "my-brand-subbrand"},
   755  				"on-model": []interface{}{"my-brand/my-model3", "my-brand-subbrand/my-model2"},
   756  			},
   757  			model: myModel2,
   758  		}, {
   759  			m: map[string]interface{}{
   760  				"on-store": []interface{}{"store2"},
   761  				"on-brand": []interface{}{"my-brand", "my-brand-subbrand"},
   762  				"on-model": []interface{}{"my-brand/my-model3", "my-brand-subbrand/my-model2"},
   763  			},
   764  			model: myModel3, store: substore1,
   765  			useFriendlyStores: true,
   766  		}, {
   767  			m: map[string]interface{}{
   768  				"on-store": []interface{}{"other-store"},
   769  				"on-brand": []interface{}{"my-brand", "my-brand-subbrand"},
   770  				"on-model": []interface{}{"my-brand/my-model3", "my-brand-subbrand/my-model2"},
   771  			},
   772  			model: myModel3, store: substore1,
   773  			useFriendlyStores: true,
   774  			err:               "on-store mismatch",
   775  		}, {
   776  			m: map[string]interface{}{
   777  				"on-store": []interface{}{"store2"},
   778  				"on-brand": []interface{}{"other-brand", "my-brand-subbrand"},
   779  				"on-model": []interface{}{"my-brand/my-model3", "my-brand-subbrand/my-model2"},
   780  			},
   781  			model: myModel3, store: substore1,
   782  			useFriendlyStores: true,
   783  			err:               "on-brand mismatch",
   784  		}, {
   785  			m: map[string]interface{}{
   786  				"on-store": []interface{}{"store2"},
   787  				"on-brand": []interface{}{"my-brand", "my-brand-subbrand"},
   788  				"on-model": []interface{}{"my-brand/my-model1", "my-brand-subbrand/my-model2"},
   789  			},
   790  			model: myModel3, store: substore1,
   791  			useFriendlyStores: true,
   792  			err:               "on-model mismatch",
   793  		}, {
   794  			m: map[string]interface{}{
   795  				"on-store": []interface{}{"store2"},
   796  				"on-brand": []interface{}{"my-brand", "my-brand-subbrand"},
   797  				"on-model": []interface{}{"my-brand/my-model1", "my-brand-subbrand/my-model2"},
   798  			},
   799  			model: myModel3, store: substore1,
   800  			useFriendlyStores: false,
   801  			err:               "on-store mismatch",
   802  		},
   803  	}
   804  
   805  	for _, t := range tests {
   806  		constr, err := asserts.CompileDeviceScopeConstraint(t.m, "constraint")
   807  		c.Assert(err, IsNil)
   808  
   809  		var opts *asserts.DeviceScopeConstraintCheckOptions
   810  		if t.useFriendlyStores {
   811  			opts = &asserts.DeviceScopeConstraintCheckOptions{
   812  				UseFriendlyStores: true,
   813  			}
   814  		}
   815  		err = constr.Check(t.model, t.store, opts)
   816  		if t.err == "" {
   817  			c.Check(err, IsNil, Commentf("%v", t.m))
   818  		} else {
   819  			c.Check(err, ErrorMatches, t.err)
   820  		}
   821  	}
   822  }