github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/daemon/api_model_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package daemon
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"net/http"
    26  	"time"
    27  
    28  	"gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/asserts/assertstest"
    32  	"github.com/snapcore/snapd/client"
    33  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    34  	"github.com/snapcore/snapd/overlord/auth"
    35  	"github.com/snapcore/snapd/overlord/devicestate"
    36  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    37  	"github.com/snapcore/snapd/overlord/hookstate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  )
    40  
    41  func (s *apiSuite) TestPostRemodelUnhappy(c *check.C) {
    42  	data, err := json.Marshal(postModelData{NewModel: "invalid model"})
    43  	c.Check(err, check.IsNil)
    44  
    45  	req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data))
    46  	c.Assert(err, check.IsNil)
    47  	rsp := postModel(appsCmd, req, nil).(*resp)
    48  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
    49  	c.Assert(rsp.Status, check.Equals, 400)
    50  	c.Check(rsp.Result.(*errorResult).Message, check.Matches, "cannot decode new model assertion: .*")
    51  }
    52  
    53  func (s *apiSuite) TestPostRemodel(c *check.C) {
    54  	oldModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
    55  	newModel := s.brands.Model("my-brand", "my-old-model", modelDefaults, map[string]interface{}{
    56  		"revision": "2",
    57  	})
    58  
    59  	d := s.daemonWithOverlordMock(c)
    60  	hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
    61  	c.Assert(err, check.IsNil)
    62  	deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
    63  	c.Assert(err, check.IsNil)
    64  	d.overlord.AddManager(deviceMgr)
    65  	st := d.overlord.State()
    66  	st.Lock()
    67  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
    68  	assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
    69  	s.mockModel(c, st, oldModel)
    70  	st.Unlock()
    71  
    72  	soon := 0
    73  	ensureStateSoon = func(st *state.State) {
    74  		soon++
    75  		ensureStateSoonImpl(st)
    76  	}
    77  	defer func() { ensureStateSoon = func(st *state.State) {} }()
    78  
    79  	var devicestateRemodelGotModel *asserts.Model
    80  	devicestateRemodel = func(st *state.State, nm *asserts.Model) (*state.Change, error) {
    81  		devicestateRemodelGotModel = nm
    82  		chg := st.NewChange("remodel", "...")
    83  		return chg, nil
    84  	}
    85  
    86  	// create a valid model assertion
    87  	c.Assert(err, check.IsNil)
    88  	modelEncoded := string(asserts.Encode(newModel))
    89  	data, err := json.Marshal(postModelData{NewModel: modelEncoded})
    90  	c.Check(err, check.IsNil)
    91  
    92  	// set it and validate that this is what we was passed to
    93  	// devicestateRemodel
    94  	req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data))
    95  	c.Assert(err, check.IsNil)
    96  	rsp := postModel(appsCmd, req, nil).(*resp)
    97  	c.Assert(rsp.Status, check.Equals, 202)
    98  	c.Check(devicestateRemodelGotModel, check.DeepEquals, newModel)
    99  
   100  	st.Lock()
   101  	defer st.Unlock()
   102  	chg := st.Change(rsp.Change)
   103  	c.Assert(chg, check.NotNil)
   104  
   105  	c.Assert(st.Changes(), check.HasLen, 1)
   106  	chg1 := st.Changes()[0]
   107  	c.Assert(chg, check.DeepEquals, chg1)
   108  	c.Assert(chg.Kind(), check.Equals, "remodel")
   109  	c.Assert(chg.Err(), check.IsNil)
   110  
   111  	c.Assert(soon, check.Equals, 1)
   112  }
   113  
   114  func (s *apiSuite) TestGetModelNoModelAssertion(c *check.C) {
   115  
   116  	d := s.daemonWithOverlordMock(c)
   117  	hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
   118  	c.Assert(err, check.IsNil)
   119  	deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
   120  	c.Assert(err, check.IsNil)
   121  	d.overlord.AddManager(deviceMgr)
   122  
   123  	req, err := http.NewRequest("GET", "/v2/model", nil)
   124  	c.Assert(err, check.IsNil)
   125  	response := getModel(appsCmd, req, nil)
   126  	c.Assert(response, check.FitsTypeOf, &resp{})
   127  	rsp := response.(*resp)
   128  	c.Assert(rsp.Status, check.Equals, 404)
   129  	c.Assert(rsp.Result, check.FitsTypeOf, &errorResult{})
   130  	errRes := rsp.Result.(*errorResult)
   131  	c.Assert(errRes.Kind, check.Equals, client.ErrorKindAssertionNotFound)
   132  	c.Assert(errRes.Value, check.Equals, "model")
   133  	c.Assert(errRes.Message, check.Equals, "no model assertion yet")
   134  }
   135  
   136  func (s *apiSuite) TestGetModelHasModelAssertion(c *check.C) {
   137  	// make a model assertion
   138  	theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
   139  
   140  	// model assertion setup
   141  	d := s.daemonWithOverlordMock(c)
   142  	hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
   143  	c.Assert(err, check.IsNil)
   144  	deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
   145  	c.Assert(err, check.IsNil)
   146  	d.overlord.AddManager(deviceMgr)
   147  	st := d.overlord.State()
   148  	st.Lock()
   149  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
   150  	assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
   151  	s.mockModel(c, st, theModel)
   152  	st.Unlock()
   153  
   154  	// make a new get request to the model endpoint
   155  	req, err := http.NewRequest("GET", "/v2/model", nil)
   156  	c.Assert(err, check.IsNil)
   157  	response := getModel(appsCmd, req, nil)
   158  
   159  	// check that we get an assertion response
   160  	c.Assert(response, check.FitsTypeOf, &assertResponse{})
   161  
   162  	// check that there is only one assertion
   163  	assertions := response.(*assertResponse).assertions
   164  	c.Assert(assertions, check.HasLen, 1)
   165  
   166  	// check that one of the assertion keys matches what's in the model we
   167  	// provided
   168  	assert := assertions[0]
   169  	arch := assert.Header("architecture")
   170  	c.Assert(arch, check.FitsTypeOf, "")
   171  	c.Assert(arch.(string), check.Equals, modelDefaults["architecture"])
   172  }
   173  
   174  func (s *apiSuite) TestGetModelJSONHasModelAssertion(c *check.C) {
   175  	// make a model assertion
   176  	theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
   177  
   178  	// model assertion setup
   179  	d := s.daemonWithOverlordMock(c)
   180  	hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
   181  	c.Assert(err, check.IsNil)
   182  	deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
   183  	c.Assert(err, check.IsNil)
   184  	d.overlord.AddManager(deviceMgr)
   185  	st := d.overlord.State()
   186  	st.Lock()
   187  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
   188  	assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
   189  	s.mockModel(c, st, theModel)
   190  	st.Unlock()
   191  
   192  	// make a new get request to the model endpoint with json as true
   193  	req, err := http.NewRequest("GET", "/v2/model?json=true", nil)
   194  	c.Assert(err, check.IsNil)
   195  	response := getModel(appsCmd, req, nil)
   196  
   197  	// check that we get an generic response type
   198  	c.Assert(response, check.FitsTypeOf, &resp{})
   199  
   200  	// get the body and try to unmarshal into modelAssertJSONResponse
   201  	c.Assert(response.(*resp).Result, check.FitsTypeOf, modelAssertJSONResponse{})
   202  
   203  	jsonResponse := response.(*resp).Result.(modelAssertJSONResponse)
   204  
   205  	// get the architecture key from the headers
   206  	arch, ok := jsonResponse.Headers["architecture"]
   207  	c.Assert(ok, check.Equals, true)
   208  
   209  	// ensure that the architecture key is what we set in the model defaults
   210  	c.Assert(arch, check.FitsTypeOf, "")
   211  	c.Assert(arch.(string), check.Equals, modelDefaults["architecture"])
   212  }
   213  
   214  func (s *apiSuite) TestGetModelNoSerialAssertion(c *check.C) {
   215  
   216  	d := s.daemonWithOverlordMock(c)
   217  	hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
   218  	c.Assert(err, check.IsNil)
   219  	deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
   220  	c.Assert(err, check.IsNil)
   221  	d.overlord.AddManager(deviceMgr)
   222  
   223  	req, err := http.NewRequest("GET", "/v2/model/serial", nil)
   224  	c.Assert(err, check.IsNil)
   225  	response := getSerial(appsCmd, req, nil)
   226  	c.Assert(response, check.FitsTypeOf, &resp{})
   227  	rsp := response.(*resp)
   228  	c.Assert(rsp.Status, check.Equals, 404)
   229  	c.Assert(rsp.Result, check.FitsTypeOf, &errorResult{})
   230  	errRes := rsp.Result.(*errorResult)
   231  	c.Assert(errRes.Kind, check.Equals, client.ErrorKindAssertionNotFound)
   232  	c.Assert(errRes.Value, check.Equals, "serial")
   233  	c.Assert(errRes.Message, check.Equals, "no serial assertion yet")
   234  }
   235  
   236  func (s *apiSuite) TestGetModelHasSerialAssertion(c *check.C) {
   237  	// make a model assertion
   238  	theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
   239  
   240  	deviceKey, _ := assertstest.GenerateKey(752)
   241  
   242  	encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey())
   243  	c.Assert(err, check.IsNil)
   244  
   245  	// model assertion setup
   246  	d := s.daemonWithOverlordMock(c)
   247  	hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
   248  	c.Assert(err, check.IsNil)
   249  	deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
   250  	c.Assert(err, check.IsNil)
   251  	d.overlord.AddManager(deviceMgr)
   252  	st := d.overlord.State()
   253  	st.Lock()
   254  	defer st.Unlock()
   255  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
   256  	assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
   257  	s.mockModel(c, st, theModel)
   258  
   259  	serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
   260  		"authority-id":        "my-brand",
   261  		"brand-id":            "my-brand",
   262  		"model":               "my-old-model",
   263  		"serial":              "serialserial",
   264  		"device-key":          string(encDevKey),
   265  		"device-key-sha3-384": deviceKey.PublicKey().ID(),
   266  		"timestamp":           time.Now().Format(time.RFC3339),
   267  	}, nil, "")
   268  	c.Assert(err, check.IsNil)
   269  	assertstatetest.AddMany(st, serial)
   270  	devicestatetest.SetDevice(st, &auth.DeviceState{
   271  		Brand:  "my-brand",
   272  		Model:  "my-old-model",
   273  		Serial: "serialserial",
   274  	})
   275  
   276  	st.Unlock()
   277  	defer st.Lock()
   278  
   279  	// make a new get request to the serial endpoint
   280  	req, err := http.NewRequest("GET", "/v2/model/serial", nil)
   281  	c.Assert(err, check.IsNil)
   282  	response := getSerial(appsCmd, req, nil)
   283  
   284  	// check that we get an assertion response
   285  	c.Assert(response, check.FitsTypeOf, &assertResponse{})
   286  
   287  	// check that there is only one assertion
   288  	assertions := response.(*assertResponse).assertions
   289  	c.Assert(assertions, check.HasLen, 1)
   290  
   291  	// check that the device key in the returned assertion matches what we
   292  	// created above
   293  	assert := assertions[0]
   294  	devKey := assert.Header("device-key")
   295  	c.Assert(devKey, check.FitsTypeOf, "")
   296  	c.Assert(devKey.(string), check.Equals, string(encDevKey))
   297  }
   298  
   299  func (s *apiSuite) TestGetModelJSONHasSerialAssertion(c *check.C) {
   300  	// make a model assertion
   301  	theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
   302  
   303  	deviceKey, _ := assertstest.GenerateKey(752)
   304  
   305  	encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey())
   306  	c.Assert(err, check.IsNil)
   307  
   308  	// model assertion setup
   309  	d := s.daemonWithOverlordMock(c)
   310  	hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
   311  	c.Assert(err, check.IsNil)
   312  	deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
   313  	c.Assert(err, check.IsNil)
   314  	d.overlord.AddManager(deviceMgr)
   315  	st := d.overlord.State()
   316  	st.Lock()
   317  	defer st.Unlock()
   318  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
   319  	assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
   320  	s.mockModel(c, st, theModel)
   321  
   322  	serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
   323  		"authority-id":        "my-brand",
   324  		"brand-id":            "my-brand",
   325  		"model":               "my-old-model",
   326  		"serial":              "serialserial",
   327  		"device-key":          string(encDevKey),
   328  		"device-key-sha3-384": deviceKey.PublicKey().ID(),
   329  		"timestamp":           time.Now().Format(time.RFC3339),
   330  	}, nil, "")
   331  	c.Assert(err, check.IsNil)
   332  	assertstatetest.AddMany(st, serial)
   333  	devicestatetest.SetDevice(st, &auth.DeviceState{
   334  		Brand:  "my-brand",
   335  		Model:  "my-old-model",
   336  		Serial: "serialserial",
   337  	})
   338  
   339  	st.Unlock()
   340  	defer st.Lock()
   341  
   342  	// make a new get request to the model endpoint with json as true
   343  	req, err := http.NewRequest("GET", "/v2/model/serial?json=true", nil)
   344  	c.Assert(err, check.IsNil)
   345  	response := getSerial(appsCmd, req, nil)
   346  
   347  	// check that we get an generic response type
   348  	c.Assert(response, check.FitsTypeOf, &resp{})
   349  
   350  	// get the body and try to unmarshal into modelAssertJSONResponse
   351  	c.Assert(response.(*resp).Result, check.FitsTypeOf, modelAssertJSONResponse{})
   352  
   353  	jsonResponse := response.(*resp).Result.(modelAssertJSONResponse)
   354  
   355  	// get the architecture key from the headers
   356  	devKey, ok := jsonResponse.Headers["device-key"]
   357  	c.Assert(ok, check.Equals, true)
   358  
   359  	// check that the device key in the returned assertion matches what we
   360  	// created above
   361  	c.Assert(devKey, check.FitsTypeOf, "")
   362  	c.Assert(devKey.(string), check.Equals, string(encDevKey))
   363  }