Skip to content

Commit

Permalink
tests/lib: support for sequence forming assertions in fakestore code (#…
Browse files Browse the repository at this point in the history
…12585)

* image,store,tests/lib: support for validation-sets in fakestore code and tooling. The support for this required us to support sequence-forming assertions when fetching them.

* tests/lib/fakestore/store: add unit tests for sequence forming assertion functionality

* tests/lib/fakestore/store: fix typo

* tests/lib/fakestore: review feedback

add missing unit test case, simplify the sequence assertion retrieval and clarify a comment. thanks @ernestl

* tests/lib/fakestore: add more explanation of the choice made for sequence value

* tests/lib/fakestore: remove rogue newline

* tests/lib/fakestore: treat 0 and negative sequence values as an error
  • Loading branch information
Meulengracht authored Apr 5, 2023
1 parent bbbd513 commit bd81886
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 19 deletions.
86 changes: 70 additions & 16 deletions tests/lib/fakestore/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"io/ioutil"
"net"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strconv"
Expand Down Expand Up @@ -739,49 +740,102 @@ func (s *Store) retrieveAssertion(bs asserts.Backstore, assertType *asserts.Asse
return a, err
}

func (s *Store) assertionsEndpoint(w http.ResponseWriter, req *http.Request) {
assertPath := strings.TrimPrefix(req.URL.Path, "/v2/assertions/")
func (s *Store) retrieveLatestSequenceFormingAssertion(bs asserts.Backstore, assertType *asserts.AssertionType, sequenceKey []string) (asserts.Assertion, error) {
a, err := bs.SequenceMemberAfter(assertType, sequenceKey, -1, assertType.MaxSupportedFormat())
if errors.Is(err, &asserts.NotFoundError{}) && s.assertFallback {
return s.fallback.SeqFormingAssertion(assertType, sequenceKey, -1, nil)
}
return a, err
}

bs, err := s.collectAssertions()
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting assertions: %v", err), 500)
return
func (s *Store) sequenceFromQueryValues(values url.Values) (int, error) {
if val, ok := values["sequence"]; ok {
// special case value of 'latest', in that case
// we return -1 to indicate we want the newest
if val[0] != "latest" {
seq, err := strconv.Atoi(val[0])
if err != nil {
return -1, fmt.Errorf("cannot parse sequence %s: %v", val[0], err)
}

// Only positive integers and 'latest' are valid
if seq <= 0 {
return -1, fmt.Errorf("the requested sequence must be above 0")
}
return seq, nil
}
}
return -1, nil
}

func (s *Store) assertTypeAndKey(urlPath string) (*asserts.AssertionType, []string, error) {
// trim the assertions prefix, and handle any query parameters
assertPath := strings.TrimPrefix(urlPath, "/v2/assertions/")
comps := strings.Split(assertPath, "/")

if len(comps) == 0 {
http.Error(w, "missing assertion type", 400)
return
return nil, nil, fmt.Errorf("missing assertion type")
}

typ := asserts.Type(comps[0])
if typ == nil {
http.Error(w, fmt.Sprintf("unknown assertion type: %s", comps[0]), 400)
return nil, nil, fmt.Errorf("unknown assertion type: %s", comps[0])
}
return typ, comps[1:], nil
}

func (s *Store) retrieveAssertionWrapper(bs asserts.Backstore, assertType *asserts.AssertionType, keyParts []string, values url.Values) (asserts.Assertion, error) {
pk := keyParts
if assertType.SequenceForming() {
seq, err := s.sequenceFromQueryValues(values)
if err != nil {
return nil, err
}

// If no sequence value was provided, or when requesting the latest sequence
// point of an assertion, we use a different method of resolving the assertion.
if seq <= 0 {
return s.retrieveLatestSequenceFormingAssertion(bs, assertType, keyParts)
}

// Otherwise append the sequence to form the primary key and use
// the default retrieval.
pk = append(pk, strconv.Itoa(seq))
}

if !assertType.AcceptablePrimaryKey(pk) {
return nil, fmt.Errorf("wrong primary key length: %v", pk)
}
return s.retrieveAssertion(bs, assertType, pk)
}

func (s *Store) assertionsEndpoint(w http.ResponseWriter, req *http.Request) {
typ, pk, err := s.assertTypeAndKey(req.URL.Path)
if err != nil {
http.Error(w, err.Error(), 400)
return
}

pk := comps[1:]
if !typ.AcceptablePrimaryKey(pk) {
http.Error(w, fmt.Sprintf("wrong primary key length: %v", comps), 400)
bs, err := s.collectAssertions()
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting assertions: %v", err), 500)
return
}

a, err := s.retrieveAssertion(bs, typ, pk)
as, err := s.retrieveAssertionWrapper(bs, typ, pk, req.URL.Query())
if errors.Is(err, &asserts.NotFoundError{}) {
w.Header().Set("Content-Type", "application/problem+json")
w.WriteHeader(404)
w.Write([]byte(`{"error-list":[{"code":"not-found","message":"not found"}]}`))
return
}
if err != nil {
http.Error(w, fmt.Sprintf("cannot retrieve assertion %v: %v", comps, err), 400)
http.Error(w, fmt.Sprintf("cannot retrieve assertion %v: %v", pk, err), 400)
return
}

w.Header().Set("Content-Type", asserts.MediaType)
w.WriteHeader(200)
w.Write(asserts.Encode(a))
w.Write(asserts.Encode(as))
}

func addSnapIDs(bs asserts.Backstore, initial map[string]string) (map[string]string, error) {
Expand Down
70 changes: 70 additions & 0 deletions tests/lib/fakestore/store/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,23 @@ timestamp: 2016-08-19T19:19:19Z
sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
AXNpZw=`
exampleValidationSet = `type: validation-set
authority-id: canonical
account-id: canonical
name: base-set
sequence: 2
revision: 1
series: 16
snaps:
-
id: yOqKhntON3vR7kwEbVPsILm7bUViPDzz
name: snap-b
presence: required
revision: 1
timestamp: 2020-11-06T09:16:26Z
sign-key-sha3-384: 7bbncP0c4RcufwReeiylCe0S7IMCn-tHLNSCgeOVmV3K-7_MzpAHgJDYeOjldefE
AXNpZw==`
)

func (s *storeTestSuite) TestAssertionsEndpointPreloaded(c *C) {
Expand Down Expand Up @@ -478,6 +495,59 @@ func (s *storeTestSuite) TestAssertionsEndpointFromAssertsDir(c *C) {
c.Check(string(body), Equals, exampleSnapRev)
}

func (s *storeTestSuite) TestAssertionsEndpointSequenceAssertion(c *C) {
err := ioutil.WriteFile(filepath.Join(s.store.assertDir, "base-set.validation-set"), []byte(exampleValidationSet), 0655)
c.Assert(err, IsNil)

resp, err := s.StoreGet(`/v2/assertions/validation-set/16/canonical/base-set?sequence=2`)
c.Assert(err, IsNil)
defer resp.Body.Close()

c.Check(resp.StatusCode, Equals, 200)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, IsNil)
c.Check(string(body), Equals, exampleValidationSet)
}

func (s *storeTestSuite) TestAssertionsEndpointSequenceAssertionLatest(c *C) {
err := ioutil.WriteFile(filepath.Join(s.store.assertDir, "base-set.validation-set"), []byte(exampleValidationSet), 0655)
c.Assert(err, IsNil)

resp, err := s.StoreGet(`/v2/assertions/validation-set/16/canonical/base-set?sequence=latest`)
c.Assert(err, IsNil)
defer resp.Body.Close()

c.Check(resp.StatusCode, Equals, 200)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, IsNil)
c.Check(string(body), Equals, exampleValidationSet)
}

func (s *storeTestSuite) TestAssertionsEndpointSequenceAssertionInvalidSequence(c *C) {
err := ioutil.WriteFile(filepath.Join(s.store.assertDir, "base-set.validation-set"), []byte(exampleValidationSet), 0655)
c.Assert(err, IsNil)

resp, err := s.StoreGet(`/v2/assertions/validation-set/16/canonical/base-set?sequence=0`)
c.Assert(err, IsNil)
defer resp.Body.Close()

c.Assert(resp.StatusCode, Equals, 400)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, IsNil)
c.Check(string(body), Equals, "cannot retrieve assertion [16 canonical base-set]: the requested sequence must be above 0\n")
}

func (s *storeTestSuite) TestAssertionsEndpointSequenceInvalid(c *C) {
resp, err := s.StoreGet(`/v2/assertions/validation-set/16/canonical/base-set?sequence=foo`)
c.Assert(err, IsNil)
defer resp.Body.Close()

c.Assert(resp.StatusCode, Equals, 400)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, IsNil)
c.Check(string(body), Equals, "cannot retrieve assertion [16 canonical base-set]: cannot parse sequence foo: strconv.Atoi: parsing \"foo\": invalid syntax\n")
}

func (s *storeTestSuite) TestAssertionsEndpointNotFound(c *C) {
// something not found
resp, err := s.StoreGet(`/v2/assertions/account/not-an-account-id`)
Expand Down
6 changes: 3 additions & 3 deletions tests/lib/gendeveloper1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ func main() {
log.Fatalf("failed to decode model headers data: %v", err)
}

assertName, _ := headers["type"]
headerType := headers["type"]
assertType := asserts.ModelType
if assertName == "system-user" {
assertType = asserts.SystemUserType
if assertTypeStr, ok := headerType.(string); ok {
assertType = asserts.Type(assertTypeStr)
}

clModel, err := devSigning.Sign(assertType, headers, nil, "")
Expand Down

0 comments on commit bd81886

Please sign in to comment.