diff options
| author | Michael Vogt <mvo@ubuntu.com> | 2016-09-14 08:56:44 +0200 |
|---|---|---|
| committer | Michael Vogt <mvo@ubuntu.com> | 2016-09-14 08:56:44 +0200 |
| commit | d437bf4ff15ac815fa8777e60e037b3a0df0d0a3 (patch) | |
| tree | 0a1589a4e6c6594182f416838df07826b7ac13df | |
| parent | 6dde596a31a8d53f55e76b55ea610f585faf5c2f (diff) | |
| parent | dd64c4015e73475fdca468944bbd06aa8fa587a1 (diff) | |
Merge remote-tracking branch 'upstream/master' into bugfix/autopkgtest-homebugfix/autopkgtest-home
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | asserts/gpgkeypairmgr.go | 12 | ||||
| -rw-r--r-- | cmd/snap/cmd_sign_test.go | 1 | ||||
| -rwxr-xr-x | debian/rules | 2 | ||||
| -rw-r--r-- | overlord/auth/auth.go | 44 | ||||
| -rw-r--r-- | overlord/auth/auth_test.go | 80 | ||||
| -rwxr-xr-x | run-checks | 8 | ||||
| -rw-r--r-- | store/store.go | 58 | ||||
| -rw-r--r-- | store/store_test.go | 1237 | ||||
| -rw-r--r-- | tests/lib/fakestore/store/store_test.go | 1 | ||||
| -rw-r--r-- | tests/main/create-key/successful_default.exp | 7 | ||||
| -rw-r--r-- | tests/main/create-key/successful_non_default.exp | 7 | ||||
| -rw-r--r-- | tests/main/create-key/task.yaml | 27 | ||||
| -rw-r--r-- | tests/main/op-remove-retry/task.yaml | 2 | ||||
| -rw-r--r-- | tests/main/writable-areas/task.yaml | 16 | ||||
| -rw-r--r-- | tests/manual-tests.md (renamed from integration-tests/manual-tests.md) | 0 |
16 files changed, 423 insertions, 1081 deletions
diff --git a/.gitignore b/.gitignore index 572bba7ac3..c075f9adab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -integration-tests/bin/ -integration-tests/data/output/ share tags .coverage diff --git a/asserts/gpgkeypairmgr.go b/asserts/gpgkeypairmgr.go index ccf5f74b3a..9024975172 100644 --- a/asserts/gpgkeypairmgr.go +++ b/asserts/gpgkeypairmgr.go @@ -83,6 +83,18 @@ func runGPGImpl(input []byte, args ...string) ([]byte, error) { return nil, err } + // Ensure the gpg-agent knows what tty to talk to to ask for + // the passphrase. This is needed because we drive gpg over + // a pipe and if the agent is not already started it will + // fail to be able to ask for a password. + if os.Getenv("GPG_TTY") == "" { + tty, err := os.Readlink("/proc/self/fd/0") + if err != nil { + return nil, err + } + os.Setenv("GPG_TTY", tty) + } + general := []string{"--homedir", homedir, "-q", "--no-auto-check-trustdb"} allArgs := append(general, args...) diff --git a/cmd/snap/cmd_sign_test.go b/cmd/snap/cmd_sign_test.go index fa797542e7..341551e7e7 100644 --- a/cmd/snap/cmd_sign_test.go +++ b/cmd/snap/cmd_sign_test.go @@ -1,5 +1,4 @@ // -*- Mode: Go; indent-tabs-mode: t -*- -// +build !integrationcoverage /* * Copyright (C) 2016 Canonical Ltd diff --git a/debian/rules b/debian/rules index 140b80de6f..ecc1743e52 100755 --- a/debian/rules +++ b/debian/rules @@ -5,7 +5,7 @@ export DH_OPTIONS export DH_GOPKG := github.com/snapcore/snapd #export DEB_BUILD_OPTIONS=nocheck -export DH_GOLANG_EXCLUDES=integration-tests tests +export DH_GOLANG_EXCLUDES=tests export DH_GOLANG_GO_GENERATE=1 export PATH:=${PATH}:${CURDIR} diff --git a/overlord/auth/auth.go b/overlord/auth/auth.go index 2f1fa46749..ad99a566a1 100644 --- a/overlord/auth/auth.go +++ b/overlord/auth/auth.go @@ -228,15 +228,17 @@ type DeviceAssertions interface { } var ( + // ErrNoSerial indicates that a device serial is not set yet. ErrNoSerial = errors.New("no device serial yet") ) // An AuthContext exposes authorization data and handles its updates. type AuthContext interface { Device() (*DeviceState, error) - UpdateDevice(device *DeviceState) error - UpdateUser(user *UserState) error + UpdateDeviceAuth(device *DeviceState, sessionMacaroon string) (actual *DeviceState, err error) + + UpdateUserAuth(user *UserState, discharges []string) (actual *UserState, err error) StoreID(fallback string) (string, error) @@ -265,20 +267,46 @@ func (ac *authContext) Device() (*DeviceState, error) { return Device(ac.state) } -// UpdateDevice updates device in state. -func (ac *authContext) UpdateDevice(device *DeviceState) error { +// UpdateDeviceAuth updates the device auth details in state. +// The last update wins but other device details are left unchanged. +// It returns the updated device state value. +func (ac *authContext) UpdateDeviceAuth(device *DeviceState, newSessionMacaroon string) (actual *DeviceState, err error) { ac.state.Lock() defer ac.state.Unlock() - return SetDevice(ac.state, device) + cur, err := Device(ac.state) + if err != nil { + return nil, err + } + + // just do it, last update wins + cur.SessionMacaroon = newSessionMacaroon + if err := SetDevice(ac.state, cur); err != nil { + return nil, fmt.Errorf("internal error: cannot update just read device state: %v", err) + } + + return cur, nil } -// UpdateUser updates user in state. -func (ac *authContext) UpdateUser(user *UserState) error { +// UpdateUserAuth updates the user auth details in state. +// The last update wins but other user details are left unchanged. +// It returns the updated user state value. +func (ac *authContext) UpdateUserAuth(user *UserState, newDischarges []string) (actual *UserState, err error) { ac.state.Lock() defer ac.state.Unlock() - return UpdateUser(ac.state, user) + cur, err := User(ac.state, user.ID) + if err != nil { + return nil, err + } + + // just do it, last update wins + cur.StoreDischarges = newDischarges + if err := UpdateUser(ac.state, cur); err != nil { + return nil, fmt.Errorf("internal error: cannot update just read user state: %v", err) + } + + return cur, nil } // StoreID returns the store id according to system state or diff --git a/overlord/auth/auth_test.go b/overlord/auth/auth_test.go index 6b0d5ffdd6..4a44107c56 100644 --- a/overlord/auth/auth_test.go +++ b/overlord/auth/auth_test.go @@ -283,16 +283,15 @@ func (as *authSuite) TestSetDevice(c *C) { c.Check(device, DeepEquals, &auth.DeviceState{Brand: "some-brand"}) } -func (as *authSuite) TestAuthContextUpdateUser(c *C) { +func (as *authSuite) TestAuthContextUpdateUserAuth(c *C) { as.state.Lock() user, _ := auth.NewUser(as.state, "username", "macaroon", []string{"discharge"}) as.state.Unlock() - user.Username = "different" - user.StoreDischarges = []string{"updated-discharge"} + newDischarges := []string{"updated-discharge"} authContext := auth.NewAuthContext(as.state, nil) - err := authContext.UpdateUser(user) + user, err := authContext.UpdateUserAuth(user, newDischarges) c.Check(err, IsNil) as.state.Lock() @@ -300,9 +299,43 @@ func (as *authSuite) TestAuthContextUpdateUser(c *C) { as.state.Unlock() c.Check(err, IsNil) c.Check(userFromState, DeepEquals, user) + c.Check(userFromState.Discharges, DeepEquals, []string{"discharge"}) + c.Check(user.StoreDischarges, DeepEquals, newDischarges) +} + +func (as *authSuite) TestAuthContextUpdateUserAuthOtherUpdate(c *C) { + as.state.Lock() + user, _ := auth.NewUser(as.state, "username", "macaroon", []string{"discharge"}) + otherUpdateUser := *user + otherUpdateUser.Macaroon = "macaroon2" + otherUpdateUser.StoreDischarges = []string{"other-discharges"} + err := auth.UpdateUser(as.state, &otherUpdateUser) + as.state.Unlock() + c.Assert(err, IsNil) + + newDischarges := []string{"updated-discharge"} + + authContext := auth.NewAuthContext(as.state, nil) + // last discharges win + curUser, err := authContext.UpdateUserAuth(user, newDischarges) + c.Assert(err, IsNil) + + as.state.Lock() + userFromState, err := auth.User(as.state, user.ID) + as.state.Unlock() + c.Check(err, IsNil) + c.Check(userFromState, DeepEquals, curUser) + c.Check(curUser, DeepEquals, &auth.UserState{ + ID: user.ID, + Username: "username", + Macaroon: "macaroon2", + Discharges: []string{"discharge"}, + StoreMacaroon: "macaroon", + StoreDischarges: newDischarges, + }) } -func (as *authSuite) TestAuthContextUpdateUserInvalid(c *C) { +func (as *authSuite) TestAuthContextUpdateUserAuthInvalid(c *C) { as.state.Lock() _, _ = auth.NewUser(as.state, "username", "macaroon", []string{"discharge"}) as.state.Unlock() @@ -314,7 +347,7 @@ func (as *authSuite) TestAuthContextUpdateUserInvalid(c *C) { } authContext := auth.NewAuthContext(as.state, nil) - err := authContext.UpdateUser(user) + _, err := authContext.UpdateUserAuth(user, nil) c.Assert(err, ErrorMatches, "invalid user") } @@ -340,21 +373,50 @@ func (as *authSuite) TestAuthContextDevice(c *C) { c.Check(deviceFromState, DeepEquals, device) } -func (as *authSuite) TestAuthContextUpdateDevice(c *C) { +func (as *authSuite) TestAuthContextUpdateDeviceAuth(c *C) { as.state.Lock() device, err := auth.Device(as.state) as.state.Unlock() c.Check(err, IsNil) c.Check(device, DeepEquals, &auth.DeviceState{}) + sessionMacaroon := "the-device-macaroon" + authContext := auth.NewAuthContext(as.state, nil) - device.SessionMacaroon = "the-device-macaroon" - err = authContext.UpdateDevice(device) + device, err = authContext.UpdateDeviceAuth(device, sessionMacaroon) c.Check(err, IsNil) deviceFromState, err := authContext.Device() c.Check(err, IsNil) c.Check(deviceFromState, DeepEquals, device) + c.Check(deviceFromState.SessionMacaroon, DeepEquals, sessionMacaroon) +} + +func (as *authSuite) TestAuthContextUpdateDeviceAuthOtherUpdate(c *C) { + as.state.Lock() + device, _ := auth.Device(as.state) + otherUpdateDevice := *device + otherUpdateDevice.SessionMacaroon = "othe-session-macaroon" + otherUpdateDevice.KeyID = "KEYID" + err := auth.SetDevice(as.state, &otherUpdateDevice) + as.state.Unlock() + c.Check(err, IsNil) + + sessionMacaroon := "the-device-macaroon" + + authContext := auth.NewAuthContext(as.state, nil) + curDevice, err := authContext.UpdateDeviceAuth(device, sessionMacaroon) + c.Assert(err, IsNil) + + as.state.Lock() + deviceFromState, err := auth.Device(as.state) + as.state.Unlock() + c.Check(err, IsNil) + c.Check(deviceFromState, DeepEquals, curDevice) + c.Check(curDevice, DeepEquals, &auth.DeviceState{ + KeyID: "KEYID", + SessionMacaroon: sessionMacaroon, + }) } func (as *authSuite) TestAuthContextStoreIDFallback(c *C) { diff --git a/run-checks b/run-checks index e89dd5b7b7..0e75ee457d 100755 --- a/run-checks +++ b/run-checks @@ -30,7 +30,7 @@ case "${1:-all}" in SPREAD=1 ;; *) - echo "Wrong flag ${1}. To run a single suite use --static, --unit, --spread, or --integration." + echo "Wrong flag ${1}. To run a single suite use --static, --unit, --spread." exit 1 esac @@ -116,12 +116,12 @@ if [ "$UNIT" = 1 ]; then echo "mode: set" > .coverage/coverage.out echo Building - go build -tags=excludeintegration -v github.com/snapcore/snapd/... + go build -v github.com/snapcore/snapd/... # tests echo Running tests from $(pwd) - for pkg in $(go list ./... | grep -v integration-tests); do - $goctest -tags=excludeintegration -v -coverprofile=.coverage/profile.out $pkg + for pkg in $(go list ./...); do + $goctest -v -coverprofile=.coverage/profile.out $pkg append_coverage .coverage/profile.out done diff --git a/store/store.go b/store/store.go index 2eaa7c1cb8..9a715a649a 100644 --- a/store/store.go +++ b/store/store.go @@ -367,52 +367,53 @@ func authenticateUser(r *http.Request, user *auth.UserState) { r.Header.Set("Authorization", buf.String()) } -// refreshMacaroon will request a refreshed discharge macaroon for the user -func refreshMacaroon(user *auth.UserState) error { +// refreshDischarges will request refreshed discharge macaroons for the user +func refreshDischarges(user *auth.UserState) ([]string, error) { + newDischarges := make([]string, len(user.StoreDischarges)) for i, d := range user.StoreDischarges { discharge, err := MacaroonDeserialize(d) if err != nil { - return err + return nil, err } - if discharge.Location() == UbuntuoneLocation { - refreshedDischarge, err := RefreshDischargeMacaroon(d) - if err != nil { - return err - } - user.StoreDischarges[i] = refreshedDischarge + if discharge.Location() != UbuntuoneLocation { + newDischarges[i] = d + continue } + + refreshedDischarge, err := RefreshDischargeMacaroon(d) + if err != nil { + return nil, err + } + newDischarges[i] = refreshedDischarge } - return nil + return newDischarges, nil } // refreshUser will refresh user discharge macaroon and update state func (s *Store) refreshUser(user *auth.UserState) error { - err := refreshMacaroon(user) + newDischarges, err := refreshDischarges(user) if err != nil { return err } if s.authContext != nil { - err = s.authContext.UpdateUser(user) + curUser, err := s.authContext.UpdateUserAuth(user, newDischarges) if err != nil { return err } + // update in place + *user = *curUser } return nil } // refreshDeviceSession will set or refresh the device session in the state -func (s *Store) refreshDeviceSession() error { +func (s *Store) refreshDeviceSession(device *auth.DeviceState) error { if s.authContext == nil { return fmt.Errorf("internal error: no authContext") } - device, err := s.authContext.Device() - if err != nil { - return err - } - nonce, err := RequestStoreDeviceNonce() if err != nil { return err @@ -428,11 +429,12 @@ func (s *Store) refreshDeviceSession() error { return err } - device.SessionMacaroon = session - err = s.authContext.UpdateDevice(device) + curDevice, err := s.authContext.UpdateDeviceAuth(device, session) if err != nil { return err } + // update in place + *device = *curDevice return nil } @@ -492,7 +494,15 @@ func (s *Store) doRequest(client *http.Client, reqOptions *requestOptions, user } if strings.Contains(wwwAuth, "refresh_device_session=1") { // refresh device session - err = s.refreshDeviceSession() + if s.authContext == nil { + return nil, fmt.Errorf("internal error: no authContext") + } + device, err := s.authContext.Device() + if err != nil { + return nil, err + } + + err = s.refreshDeviceSession(device) if err != nil { return nil, err } @@ -526,8 +536,10 @@ func (s *Store) newRequest(reqOptions *requestOptions, user *auth.UserState) (*h if err != nil { return nil, err } - if device.SessionMacaroon == "" { - err = s.refreshDeviceSession() + // we don't have a session yet but have a serial, try + // to get a session + if device.SessionMacaroon == "" && device.Serial != "" { + err = s.refreshDeviceSession(device) if err == auth.ErrNoSerial { // missing serial assertion, log and continue without device authentication logger.Debugf("cannot set device session: %v", err) diff --git a/store/store_test.go b/store/store_test.go index 19cfe11d34..90f539b3e0 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -102,17 +102,23 @@ type testAuthContext struct { } func (ac *testAuthContext) Device() (*auth.DeviceState, error) { - return ac.device, nil + freshDevice := *ac.device + return &freshDevice, nil } -func (ac *testAuthContext) UpdateDevice(d *auth.DeviceState) error { - ac.device = d - return nil +func (ac *testAuthContext) UpdateDeviceAuth(d *auth.DeviceState, newSessionMacaroon string) (*auth.DeviceState, error) { + ac.c.Assert(d, DeepEquals, ac.device) + updated := *ac.device + updated.SessionMacaroon = newSessionMacaroon + *ac.device = updated + return &updated, nil } -func (ac *testAuthContext) UpdateUser(u *auth.UserState) error { +func (ac *testAuthContext) UpdateUserAuth(u *auth.UserState, newDischarges []string) (*auth.UserState, error) { ac.c.Assert(u, DeepEquals, ac.user) - return nil + updated := *ac.user + updated.StoreDischarges = newDischarges + return &updated, nil } func (ac *testAuthContext) StoreID(fallback string) (string, error) { @@ -199,6 +205,7 @@ func createTestDevice() *auth.DeviceState { return &auth.DeviceState{ Brand: "some-brand", SessionMacaroon: "device-macaroon", + Serial: "9999", } } @@ -336,6 +343,180 @@ func (t *remoteRepoTestSuite) TestDownloadSyncFails(c *C) { c.Assert(osutil.FileExists(tmpfile.Name()), Equals, false) } +func (t *remoteRepoTestSuite) TestDoRequestSetsAuth(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.UserAgent(), Equals, userAgent) + // check user authorization is set + authorization := r.Header.Get("Authorization") + c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) + // check device authorization is set + c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + + io.WriteString(w, "response-data") + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + authContext := &testAuthContext{c: c, device: t.device, user: t.user} + repo := New(&Config{}, authContext) + c.Assert(repo, NotNil) + + endpoint, _ := url.Parse(mockServer.URL) + reqOptions := &requestOptions{Method: "GET", URL: endpoint} + + response, err := repo.doRequest(repo.client, reqOptions, t.user) + defer response.Body.Close() + c.Assert(err, IsNil) + + responseData, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Check(string(responseData), Equals, "response-data") +} + +func (t *remoteRepoTestSuite) TestDoRequestAuthNoSerial(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.UserAgent(), Equals, userAgent) + // check user authorization is set + authorization := r.Header.Get("Authorization") + c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) + // check device authorization was not set + c.Check(r.Header.Get("X-Device-Authorization"), Equals, "") + + io.WriteString(w, "response-data") + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + // no serial and no device macaroon => no device auth + t.device.Serial = "" + t.device.SessionMacaroon = "" + authContext := &testAuthContext{c: c, device: t.device, user: t.user} + repo := New(&Config{}, authContext) + c.Assert(repo, NotNil) + + endpoint, _ := url.Parse(mockServer.URL) + reqOptions := &requestOptions{Method: "GET", URL: endpoint} + + response, err := repo.doRequest(repo.client, reqOptions, t.user) + defer response.Body.Close() + c.Assert(err, IsNil) + + responseData, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Check(string(responseData), Equals, "response-data") +} + +func (t *remoteRepoTestSuite) TestDoRequestRefreshesAuth(c *C) { + refresh, err := makeTestRefreshDischargeResponse() + c.Assert(err, IsNil) + c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) + + // mock refresh response + refreshDischargeEndpointHit := false + mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) + refreshDischargeEndpointHit = true + })) + defer mockSSOServer.Close() + UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" + + // mock store response (requiring auth refresh) + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.UserAgent(), Equals, userAgent) + + authorization := r.Header.Get("Authorization") + c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) + if t.user.StoreDischarges[0] == refresh { + io.WriteString(w, "response-data") + } else { + w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") + w.WriteHeader(http.StatusUnauthorized) + } + })) + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + authContext := &testAuthContext{c: c, device: t.device, user: t.user} + repo := New(&Config{}, authContext) + c.Assert(repo, NotNil) + + endpoint, _ := url.Parse(mockServer.URL) + reqOptions := &requestOptions{Method: "GET", URL: endpoint} + + response, err := repo.doRequest(repo.client, reqOptions, t.user) + defer response.Body.Close() + c.Assert(err, IsNil) + + responseData, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Check(string(responseData), Equals, "response-data") + c.Check(refreshDischargeEndpointHit, Equals, true) +} + +func (t *remoteRepoTestSuite) TestDoRequestSetsAndRefreshesDeviceAuth(c *C) { + deviceSessionRequested := false + refreshSessionRequested := false + expiredAuth := `Macaroon root="expired-session-macaroon"` + // mock store response + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.UserAgent(), Equals, userAgent) + + switch r.URL.Path { + case "/": + authorization := r.Header.Get("X-Device-Authorization") + if authorization == "" { + c.Fatalf("device authentication missing") + } else if authorization == expiredAuth { + w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") + w.WriteHeader(http.StatusUnauthorized) + } else { + c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) + io.WriteString(w, "response-data") + } + case "/identity/api/v1/nonces": + io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) + case "/identity/api/v1/sessions": + authorization := r.Header.Get("X-Device-Authorization") + if authorization == "" { + io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) + deviceSessionRequested = true + } else { + c.Check(authorization, Equals, expiredAuth) + io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) + refreshSessionRequested = true + } + default: + c.Fatalf("unexpected path %q", r.URL.Path) + } + })) + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" + MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" + + // make sure device session is not set + t.device.SessionMacaroon = "" + authContext := &testAuthContext{c: c, device: t.device, user: t.user} + repo := New(&Config{}, authContext) + c.Assert(repo, NotNil) + + endpoint, _ := url.Parse(mockServer.URL) + reqOptions := &requestOptions{Method: "GET", URL: endpoint} + + response, err := repo.doRequest(repo.client, reqOptions, t.user) + defer response.Body.Close() + c.Assert(err, IsNil) + + responseData, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Check(string(responseData), Equals, "response-data") + c.Check(deviceSessionRequested, Equals, true) + c.Check(refreshSessionRequested, Equals, true) +} + const ( funkyAppName = "8nzc1x4iim2xj1g2ul64" funkyAppDeveloper = "chipaca" @@ -469,6 +650,9 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetails(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.UserAgent(), Equals, userAgent) + // check device authorization is set, implicitly checking doRequest was used + c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + // no store ID by default storeID := r.Header.Get("X-Ubuntu-Store") c.Check(storeID, Equals, "") @@ -497,7 +681,8 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetails(c *C) { cfg := Config{ DetailsURI: detailsURI, } - repo := New(&cfg, nil) + authContext := &testAuthContext{c: c, device: t.device} + repo := New(&cfg, authContext) c.Assert(repo, NotNil) // the actual test @@ -593,6 +778,10 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryStoreIDFromAuthContext(c func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryRevision(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/dev/api/snap-purchases") { + w.WriteHeader(http.StatusNotFound) + return + } c.Check(r.URL.Path, Equals, "/details/hello-world") c.Check(r.URL.Query(), DeepEquals, url.Values{ "channel": []string{""}, @@ -605,11 +794,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryRevision(c *C) { c.Assert(mockServer, NotNil) defer mockServer.Close() - + purchasesURI, err := url.Parse(mockServer.URL + "/dev/api/snap-purchases/") detailsURI, err := url.Parse(mockServer.URL + "/details/") c.Assert(err, IsNil) cfg := DefaultConfig() cfg.DetailsURI = detailsURI + cfg.PurchasesURI = purchasesURI repo := New(cfg, nil) c.Assert(repo, NotNil) @@ -662,177 +852,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsDevmode(c *C) { c.Check(snap.Validate(result), IsNil) } -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsSetsAuth(c *C) { - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check user authorization is set - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - // check device authorization is set - c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) - c.Check(r.UserAgent(), Equals, userAgent) - - io.WriteString(w, MockDetailsJSON) - })) - - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - // check user authorization is set - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - // check device authorization is set - c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) - - c.Check(r.URL.Path, Equals, "/dev/api/snap-purchases/"+helloWorldSnapID+"/") - c.Check(r.URL.Query().Get("include_item_purchases"), Equals, "true") - io.WriteString(w, mockPurchaseJSON) - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - - detailsURI, err := url.Parse(mockServer.URL + "/details/") - c.Assert(err, IsNil) - purchasesURI, err := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - c.Assert(err, IsNil) - - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - DetailsURI: detailsURI, - PurchasesURI: purchasesURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - snap, err := repo.Snap("hello-world", "edge", false, snap.R(0), t.user) - c.Assert(snap, NotNil) - c.Assert(err, IsNil) - c.Check(snap.MustBuy, Equals, false) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsRefreshesAuth(c *C) { - refresh, err := makeTestRefreshDischargeResponse() - c.Assert(err, IsNil) - c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) - - // mock refresh response - refreshDischargeEndpointHit := false - mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) - refreshDischargeEndpointHit = true - })) - defer mockSSOServer.Close() - UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" - - // mock purchases response - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, mockPurchaseJSON) - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - purchasesURI, _ := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - - // mock store response (requiring auth refresh) - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - if t.user.StoreDischarges[0] == refresh { - io.WriteString(w, MockDetailsJSON) - } else { - w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") - w.WriteHeader(http.StatusUnauthorized) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - detailsURI, _ := url.Parse(mockServer.URL + "/details/") - - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - DetailsURI: detailsURI, - PurchasesURI: purchasesURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - snap, err := repo.Snap("hello-world", "edge", false, snap.R(0), t.user) - c.Assert(err, IsNil) - c.Check(refreshDischargeEndpointHit, Equals, true) - c.Assert(snap, NotNil) - c.Check(snap.MustBuy, Equals, false) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsSetsAndRefreshesDeviceAuth(c *C) { - // mock purchases response - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, mockPurchaseJSON) - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - purchasesURI, _ := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - - deviceSessionRequested := false - refreshSessionRequested := false - expiredAuth := `Macaroon root="expired-session-macaroon"` - // mock store response - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - switch r.URL.Path { - case "/details/hello-world": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - c.Fatalf("device authentication missing") - } else if authorization == expiredAuth { - w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") - w.WriteHeader(http.StatusUnauthorized) - } else { - c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) - io.WriteString(w, MockDetailsJSON) - } - case "/identity/api/v1/nonces": - io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) - deviceSessionRequested = true - } else { - c.Check(authorization, Equals, expiredAuth) - io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) - refreshSessionRequested = true - } - default: - c.Fatalf("unexpected path %q", r.URL.Path) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" - detailsURI, _ := url.Parse(mockServer.URL + "/details/") - - // make sure device session is not set - t.device.SessionMacaroon = "" - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - DetailsURI: detailsURI, - PurchasesURI: purchasesURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - snap, err := repo.Snap("hello-world", "edge", false, snap.R(0), t.user) - c.Assert(err, IsNil) - c.Assert(snap, NotNil) - c.Check(deviceSessionRequested, Equals, true) - c.Check(refreshSessionRequested, Equals, true) - c.Check(snap.MustBuy, Equals, false) -} - func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsOopses(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.URL.Path, Equals, "/details/hello-world") @@ -975,6 +994,9 @@ const MockSearchJSON = `{ func (t *remoteRepoTestSuite) TestUbuntuStoreFindQueries(c *C) { n := 0 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check device authorization is set, implicitly checking doRequest was used + c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + query := r.URL.Query() name := query.Get("name") @@ -1005,7 +1027,8 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFindQueries(c *C) { DetailsURI: detailsURI, SearchURI: searchURI, } - repo := New(&cfg, nil) + authContext := &testAuthContext{c: c, device: t.device} + repo := New(&cfg, authContext) c.Assert(repo, NotNil) for _, query := range []Search{ @@ -1143,193 +1166,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFindBadBody(c *C) { c.Check(snaps, HasLen, 0) } -func (t *remoteRepoTestSuite) TestUbuntuStoreFindSetsAuth(c *C) { - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - // check user authorization is set - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - // check device authorization is set - c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) - - c.Check(r.URL.Query().Get("q"), Equals, "foo") - w.Header().Set("Content-Type", "application/hal+json") - io.WriteString(w, MockSearchJSON) - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - // check device authorization is set - c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) - c.Check(r.URL.Path, Equals, "/dev/api/snap-purchases/"+helloWorldSnapID+"/") - c.Check(r.URL.Query().Get("include_item_purchases"), Equals, "true") - io.WriteString(w, mockPurchaseJSON) - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - - var err error - searchURI, err := url.Parse(mockServer.URL) - c.Assert(err, IsNil) - purchasesURI, err := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - c.Assert(err, IsNil) - - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - SearchURI: searchURI, - PurchasesURI: purchasesURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - snaps, err := repo.Find(&Search{Query: "foo"}, t.user) - c.Assert(err, IsNil) - c.Assert(snaps, HasLen, 1) - c.Check(snaps[0].SnapID, Equals, helloWorldSnapID) - c.Check(snaps[0].Prices, DeepEquals, map[string]float64{"EUR": 2.99, "USD": 3.49}) - c.Check(snaps[0].MustBuy, Equals, false) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreFindRefreshesAuth(c *C) { - refresh, err := makeTestRefreshDischargeResponse() - c.Assert(err, IsNil) - c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) - - // mock refresh response - refreshDischargeEndpointHit := false - mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) - refreshDischargeEndpointHit = true - })) - defer mockSSOServer.Close() - UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" - - // mock purchases response - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - io.WriteString(w, mockPurchaseJSON) - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - purchasesURI, err := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - c.Assert(err, IsNil) - - // mock store response (requiring auth refresh) - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - if t.user.StoreDischarges[0] == refresh { - c.Check(r.URL.Query().Get("q"), Equals, "foo") - w.Header().Set("Content-Type", "application/hal+json") - w.WriteHeader(http.StatusOK) - io.WriteString(w, MockSearchJSON) - } else { - w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") - w.WriteHeader(http.StatusUnauthorized) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - searchURI, _ := url.Parse(mockServer.URL) - cfg := Config{ - SearchURI: searchURI, - PurchasesURI: purchasesURI, - } - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - snaps, err := repo.Find(&Search{Query: "foo"}, t.user) - c.Assert(err, IsNil) - c.Check(refreshDischargeEndpointHit, Equals, true) - c.Assert(snaps, HasLen, 1) - c.Check(snaps[0].SnapID, Equals, helloWorldSnapID) - c.Check(snaps[0].Prices, DeepEquals, map[string]float64{"EUR": 2.99, "USD": 3.49}) - c.Check(snaps[0].MustBuy, Equals, false) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreFindSetsAndRefreshesDeviceAuth(c *C) { - // mock purchases response - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - io.WriteString(w, mockPurchaseJSON) - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - purchasesURI, err := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - c.Assert(err, IsNil) - - deviceSessionRequested := false - refreshSessionRequested := false - expiredAuth := `Macaroon root="expired-session-macaroon"` - // mock store response - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - switch r.URL.Path { - case "/": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - c.Fatalf("device authentication missing") - } else if authorization == expiredAuth { - w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") - w.WriteHeader(http.StatusUnauthorized) - } else { - c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) - c.Check(r.URL.Query().Get("q"), Equals, "foo") - w.Header().Set("Content-Type", "application/hal+json") - w.WriteHeader(http.StatusOK) - io.WriteString(w, MockSearchJSON) - } - case "/identity/api/v1/nonces": - io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) - deviceSessionRequested = true - } else { - c.Check(authorization, Equals, expiredAuth) - io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) - refreshSessionRequested = true - } - default: - c.Fatalf("unexpected path %q", r.URL.Path) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" - searchURI, _ := url.Parse(mockServer.URL) - - cfg := Config{ - SearchURI: searchURI, - PurchasesURI: purchasesURI, - } - // make sure device session is not set - t.device.SessionMacaroon = "" - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - snaps, err := repo.Find(&Search{Query: "foo"}, t.user) - c.Assert(err, IsNil) - c.Check(deviceSessionRequested, Equals, true) - c.Check(refreshSessionRequested, Equals, true) - c.Assert(snaps, HasLen, 1) - c.Check(snaps[0].SnapID, Equals, helloWorldSnapID) - c.Check(snaps[0].Prices, DeepEquals, map[string]float64{"EUR": 2.99, "USD": 3.49}) - c.Check(snaps[0].MustBuy, Equals, false) -} - func (t *remoteRepoTestSuite) TestUbuntuStoreFindAuthFailed(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check authorization is set @@ -1436,6 +1272,9 @@ var MockUpdatesJSON = ` func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefresh(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check device authorization is set, implicitly checking doRequest was used + c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + jsonReq, err := ioutil.ReadAll(r.Body) c.Assert(err, IsNil) var resp struct { @@ -1468,7 +1307,8 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefresh(c *C) { cfg := Config{ BulkURI: bulkURI, } - repo := New(&cfg, nil) + authContext := &testAuthContext{c: c, device: t.device} + repo := New(&cfg, authContext) c.Assert(repo, NotNil) results, err := repo.ListRefresh([]*RefreshCandidate{ @@ -1633,159 +1473,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdateNotSendLocalRevs(c c.Assert(err, IsNil) } -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdatesSetsAuth(c *C) { - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check user authorization is set - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - // check device authorization is set - c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) - - io.WriteString(w, MockUpdatesJSON) - })) - - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - var err error - bulkURI, err := url.Parse(mockServer.URL + "/updates/") - c.Assert(err, IsNil) - - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - BulkURI: bulkURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - _, err = repo.ListRefresh([]*RefreshCandidate{ - { - SnapID: helloWorldSnapID, - Channel: "stable", - Revision: snap.R(1), - Epoch: "0", - DevMode: false, - }, - }, t.user) - c.Assert(err, IsNil) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdatesRefreshesAuth(c *C) { - refresh, err := makeTestRefreshDischargeResponse() - c.Assert(err, IsNil) - c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) - - // mock refresh response - refreshDischargeEndpointHit := false - mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) - refreshDischargeEndpointHit = true - })) - defer mockSSOServer.Close() - UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" - - // mock store response (requiring auth refresh) - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - if t.user.StoreDischarges[0] == refresh { - io.WriteString(w, MockUpdatesJSON) - } else { - w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") - w.WriteHeader(http.StatusUnauthorized) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - bulkURI, _ := url.Parse(mockServer.URL + "/updates/") - - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - BulkURI: bulkURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - _, err = repo.ListRefresh([]*RefreshCandidate{ - { - SnapID: helloWorldSnapID, - Channel: "stable", - Revision: snap.R(1), - Epoch: "0", - DevMode: false, - }, - }, t.user) - c.Assert(err, IsNil) - c.Check(refreshDischargeEndpointHit, Equals, true) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdatesSetsAndRefreshesDeviceAuth(c *C) { - deviceSessionRequested := false - refreshSessionRequested := false - expiredAuth := `Macaroon root="expired-session-macaroon"` - // mock store response - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - switch r.URL.Path { - case "/updates/": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - c.Fatalf("device authentication missing") - } else if authorization == expiredAuth { - w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") - w.WriteHeader(http.StatusUnauthorized) - } else { - c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) - io.WriteString(w, MockUpdatesJSON) - } - case "/identity/api/v1/nonces": - io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) - deviceSessionRequested = true - } else { - c.Check(authorization, Equals, expiredAuth) - io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) - refreshSessionRequested = true - } - default: - c.Fatalf("unexpected path %q", r.URL.Path) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" - bulkURI, _ := url.Parse(mockServer.URL + "/updates/") - - // make sure device session is not set - t.device.SessionMacaroon = "" - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - BulkURI: bulkURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - _, err := repo.ListRefresh([]*RefreshCandidate{ - { - SnapID: helloWorldSnapID, - Channel: "stable", - Revision: snap.R(1), - Epoch: "0", - DevMode: false, - }, - }, t.user) - c.Assert(err, IsNil) - c.Check(deviceSessionRequested, Equals, true) - c.Check(refreshSessionRequested, Equals, true) -} - func (t *remoteRepoTestSuite) TestStructFieldsSurvivesNoTag(c *C) { type s struct { Foo int `json:"hello"` @@ -1873,34 +1560,7 @@ xS4u9rVT6UY=` func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertion(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") - c.Check(r.URL.Path, Equals, "/assertions/snap-declaration/16/snapidfoo") - io.WriteString(w, testAssertion) - })) - - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - var err error - assertionsURI, err := url.Parse(mockServer.URL + "/assertions/") - c.Assert(err, IsNil) - cfg := Config{ - AssertionsURI: assertionsURI, - } - repo := New(&cfg, nil) - - a, err := repo.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil) - c.Assert(err, IsNil) - c.Check(a, NotNil) - c.Check(a.Type(), Equals, asserts.SnapDeclarationType) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertionSetsAuth(c *C) { - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check user authorization is set - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - // check device authorization is set + // check device authorization is set, implicitly checking doRequest was used c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") @@ -1915,115 +1575,16 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertionSetsAuth(c *C) { assertionsURI, err := url.Parse(mockServer.URL + "/assertions/") c.Assert(err, IsNil) - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - AssertionsURI: assertionsURI, - } - repo := New(&cfg, authContext) - - _, err = repo.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, t.user) - c.Assert(err, IsNil) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertionRefreshesAuth(c *C) { - refresh, err := makeTestRefreshDischargeResponse() - c.Assert(err, IsNil) - c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) - - // mock refresh response - refreshDischargeEndpointHit := false - mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) - refreshDischargeEndpointHit = true - })) - defer mockSSOServer.Close() - UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" - - // mock store response (requiring auth refresh) - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - if t.user.StoreDischarges[0] == refresh { - c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") - c.Check(r.URL.Path, Equals, "/assertions/snap-declaration/16/snapidfoo") - io.WriteString(w, testAssertion) - } else { - w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") - w.WriteHeader(http.StatusUnauthorized) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - assertionsURI, _ := url.Parse(mockServer.URL + "/assertions/") - - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - AssertionsURI: assertionsURI, - } - repo := New(&cfg, authContext) - - _, err = repo.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, t.user) - c.Assert(err, IsNil) - c.Check(refreshDischargeEndpointHit, Equals, true) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertionSetsAndRefreshesDeviceAuth(c *C) { - deviceSessionRequested := false - refreshSessionRequested := false - expiredAuth := `Macaroon root="expired-session-macaroon"` - // mock store response - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - switch r.URL.Path { - case "/assertions/snap-declaration/16/snapidfoo": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - c.Fatalf("device authentication missing") - } else if authorization == expiredAuth { - w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") - w.WriteHeader(http.StatusUnauthorized) - } else { - c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) - c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") - io.WriteString(w, testAssertion) - } - case "/identity/api/v1/nonces": - io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) - deviceSessionRequested = true - } else { - c.Check(authorization, Equals, expiredAuth) - io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) - refreshSessionRequested = true - } - default: - c.Fatalf("unexpected path %q", r.URL.Path) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" - assertionsURI, _ := url.Parse(mockServer.URL + "/assertions/") - - // make sure device session is not set - t.device.SessionMacaroon = "" - authContext := &testAuthContext{c: c, device: t.device, user: t.user} cfg := Config{ AssertionsURI: assertionsURI, } + authContext := &testAuthContext{c: c, device: t.device} repo := New(&cfg, authContext) - _, err := repo.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, t.user) + a, err := repo.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil) c.Assert(err, IsNil) - c.Check(deviceSessionRequested, Equals, true) - c.Check(refreshSessionRequested, Equals, true) + c.Check(a, NotNil) + c.Check(a.Type(), Equals, asserts.SnapDeclarationType) } func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryNotFound(c *C) { @@ -2096,8 +1657,10 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositorySuggestedCurrency(c *C) { func (t *remoteRepoTestSuite) TestUbuntuStoreDecoratePurchases(c *C) { mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) + // check device authorization is set, implicitly checking doRequest was used c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + + c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) c.Check(r.URL.Path, Equals, "/dev/api/snap-purchases/") io.WriteString(w, mockPurchasesJSON) })) @@ -2142,147 +1705,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreDecoratePurchases(c *C) { c.Check(otherApp2.MustBuy, Equals, false) } -func (t *remoteRepoTestSuite) TestUbuntuStoreDecoratePurchasesRefreshesAuth(c *C) { - refresh, err := makeTestRefreshDischargeResponse() - c.Assert(err, IsNil) - c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) - - // mock refresh response - refreshDischargeEndpointHit := false - mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) - refreshDischargeEndpointHit = true - })) - defer mockSSOServer.Close() - UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" - - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - if t.user.StoreDischarges[0] == refresh { - io.WriteString(w, mockPurchasesJSON) - } else { - w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") - w.WriteHeader(http.StatusUnauthorized) - } - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - purchasesURI, _ := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - PurchasesURI: purchasesURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - helloWorld := &snap.Info{} - helloWorld.SnapID = helloWorldSnapID - helloWorld.Prices = map[string]float64{"USD": 1.23} - - funkyApp := &snap.Info{} - funkyApp.SnapID = funkyAppSnapID - funkyApp.Prices = map[string]float64{"USD": 2.34} - - otherApp := &snap.Info{} - otherApp.SnapID = "other" - otherApp.Prices = map[string]float64{"USD": 3.45} - - otherApp2 := &snap.Info{} - otherApp2.SnapID = "other2" - - snaps := []*snap.Info{helloWorld, funkyApp, otherApp, otherApp2} - - err = repo.decoratePurchases(snaps, "edge", t.user) - c.Assert(err, IsNil) - c.Check(refreshDischargeEndpointHit, Equals, true) - - c.Check(helloWorld.MustBuy, Equals, false) - c.Check(funkyApp.MustBuy, Equals, false) - c.Check(otherApp.MustBuy, Equals, true) - c.Check(otherApp2.MustBuy, Equals, false) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreDecoratePurchasesSetsAndRefreshesDeviceAuth(c *C) { - deviceSessionRequested := false - refreshSessionRequested := false - expiredAuth := `Macaroon root="expired-session-macaroon"` - // mock store response - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - switch r.URL.Path { - case "/dev/api/snap-purchases/": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - c.Fatalf("device authentication missing") - } else if authorization == expiredAuth { - w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") - w.WriteHeader(http.StatusUnauthorized) - } else { - c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) - io.WriteString(w, mockPurchasesJSON) - } - case "/identity/api/v1/nonces": - io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) - deviceSessionRequested = true - } else { - c.Check(authorization, Equals, expiredAuth) - io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) - refreshSessionRequested = true - } - default: - c.Fatalf("unexpected path %q", r.URL.Path) - } - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - MyAppsDeviceNonceAPI = mockPurchasesServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockPurchasesServer.URL + "/identity/api/v1/sessions" - purchasesURI, _ := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - - // make sure device session is not set - t.device.SessionMacaroon = "" - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - PurchasesURI: purchasesURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - helloWorld := &snap.Info{} - helloWorld.SnapID = helloWorldSnapID - helloWorld.Prices = map[string]float64{"USD": 1.23} - - funkyApp := &snap.Info{} - funkyApp.SnapID = funkyAppSnapID - funkyApp.Prices = map[string]float64{"USD": 2.34} - - otherApp := &snap.Info{} - otherApp.SnapID = "other" - otherApp.Prices = map[string]float64{"USD": 3.45} - - otherApp2 := &snap.Info{} - otherApp2.SnapID = "other2" - - snaps := []*snap.Info{helloWorld, funkyApp, otherApp, otherApp2} - - err := repo.decoratePurchases(snaps, "edge", t.user) - c.Assert(err, IsNil) - c.Check(deviceSessionRequested, Equals, true) - c.Check(refreshSessionRequested, Equals, true) - - c.Check(helloWorld.MustBuy, Equals, false) - c.Check(funkyApp.MustBuy, Equals, false) - c.Check(otherApp.MustBuy, Equals, true) - c.Check(otherApp2.MustBuy, Equals, false) -} - func (t *remoteRepoTestSuite) TestUbuntuStoreDecoratePurchasesFailedAccess(c *C) { mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) @@ -2559,15 +1981,19 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreBuySuccess(c *C) { mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": - c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) + // check device authorization is set, implicitly checking doRequest was used c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + + c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) c.Check(r.URL.Path, Equals, "/dev/api/snap-purchases/"+helloWorldSnapID+"/") c.Check(r.URL.Query().Get("include_item_purchases"), Equals, "true") io.WriteString(w, "{}") purchaseServerGetCalled = true case "POST": - c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) + // check device authorization is set, implicitly checking doRequest was used c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + + c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) c.Check(r.Header.Get("Content-Type"), Equals, "application/json") c.Check(r.URL.Path, Equals, "/dev/api/snap-purchases/") jsonReq, err := ioutil.ReadAll(r.Body) @@ -2700,123 +2126,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreBuyInteractive(c *C) { c.Check(purchaseServerPostCalled, Equals, true) } -func (t *remoteRepoTestSuite) TestUbuntuStoreBuyRefreshesAuth(c *C) { - refresh, err := makeTestRefreshDischargeResponse() - c.Assert(err, IsNil) - c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) - - // mock refresh response - refreshDischargeEndpointHit := false - mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) - refreshDischargeEndpointHit = true - })) - defer mockSSOServer.Close() - UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" - - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - if t.user.StoreDischarges[0] == refresh { - io.WriteString(w, mockSinglePurchaseJSON) - } else { - w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") - w.WriteHeader(http.StatusUnauthorized) - } - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - - purchasesURI, _ := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - cfg := Config{ - PurchasesURI: purchasesURI, - } - repo := New(&cfg, nil) - c.Assert(repo, NotNil) - - result, err := repo.Buy(&BuyOptions{ - SnapID: helloWorldSnapID, - SnapName: "hello-world", - Currency: "EUR", - Price: 0.99, - - BackendID: "123", - MethodID: 234, - }, t.user) - - c.Assert(result, NotNil) - c.Check(result.State, Equals, "Complete") - c.Check(refreshDischargeEndpointHit, Equals, true) -} - -func (t *remoteRepoTestSuite) TestUbuntuStoreBuySetsAndRefreshesDeviceAuth(c *C) { - deviceSessionRequested := false - refreshSessionRequested := false - expiredAuth := `Macaroon root="expired-session-macaroon"` - // mock store response - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - switch r.URL.Path { - case "/dev/api/snap-purchases/": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - c.Fatalf("device authentication missing") - } else if authorization == expiredAuth { - w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") - w.WriteHeader(http.StatusUnauthorized) - } else { - c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) - io.WriteString(w, mockSinglePurchaseJSON) - } - case "/identity/api/v1/nonces": - io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) - deviceSessionRequested = true - } else { - c.Check(authorization, Equals, expiredAuth) - io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) - refreshSessionRequested = true - } - default: - c.Fatalf("unexpected path %q", r.URL.Path) - } - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - MyAppsDeviceNonceAPI = mockPurchasesServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockPurchasesServer.URL + "/identity/api/v1/sessions" - purchasesURI, _ := url.Parse(mockPurchasesServer.URL + "/dev/api/snap-purchases/") - - // make sure device session is not set - t.device.SessionMacaroon = "" - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - PurchasesURI: purchasesURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - result, err := repo.Buy(&BuyOptions{ - SnapID: helloWorldSnapID, - SnapName: "hello-world", - Currency: "EUR", - Price: 0.99, - - BackendID: "123", - MethodID: 234, - }, t.user) - - c.Assert(err, IsNil) - c.Assert(result, NotNil) - c.Check(result.State, Equals, "Complete") - c.Check(deviceSessionRequested, Equals, true) - c.Check(refreshSessionRequested, Equals, true) -} - func (t *remoteRepoTestSuite) TestUbuntuStoreBuyFailWrongPrice(c *C) { searchServerCalled := false mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -3075,8 +2384,10 @@ func (t *remoteRepoTestSuite) TestUbuntuStorePaymentMethods(c *C) { mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": - c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) + // check device authorization is set, implicitly checking doRequest was used c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + + c.Check(r.Header.Get("Authorization"), Equals, t.expectedAuthorization(c, t.user)) c.Check(r.URL.Path, Equals, "/api/2.0/click/paymentmethods/") io.WriteString(w, paymentMethodsJson) purchaseServerGetCalled++ @@ -3175,101 +2486,3 @@ func (t *remoteRepoTestSuite) TestUbuntuStorePaymentMethodsHandles401(c *C) { c.Assert(err, NotNil) c.Check(err.Error(), Equals, "invalid credentials") } - -func (t *remoteRepoTestSuite) TestUbuntuStorePaymentMethodsRefreshesAuth(c *C) { - refresh, err := makeTestRefreshDischargeResponse() - c.Assert(err, IsNil) - c.Check(t.user.StoreDischarges[0], Not(Equals), refresh) - - // mock refresh response - refreshDischargeEndpointHit := false - mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh)) - refreshDischargeEndpointHit = true - })) - defer mockSSOServer.Close() - UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh" - - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authorization := r.Header.Get("Authorization") - c.Check(authorization, Equals, t.expectedAuthorization(c, t.user)) - if t.user.StoreDischarges[0] == refresh { - io.WriteString(w, paymentMethodsJson) - } else { - w.Header().Set("WWW-Authenticate", "Macaroon needs_refresh=1") - w.WriteHeader(http.StatusUnauthorized) - } - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - - paymentMethodsURI, _ := url.Parse(mockPurchasesServer.URL + "/api/2.0/click/paymentmethods/") - cfg := Config{ - PaymentMethodsURI: paymentMethodsURI, - } - repo := New(&cfg, nil) - c.Assert(repo, NotNil) - - result, err := repo.PaymentMethods(t.user) - c.Assert(err, IsNil) - c.Assert(result, NotNil) - c.Check(refreshDischargeEndpointHit, Equals, true) -} - -func (t *remoteRepoTestSuite) TestUbuntuStorePaymentMethodsSetsAndRefreshesDeviceAuth(c *C) { - deviceSessionRequested := false - refreshSessionRequested := false - expiredAuth := `Macaroon root="expired-session-macaroon"` - // mock store response - mockPurchasesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.UserAgent(), Equals, userAgent) - - switch r.URL.Path { - case "/api/2.0/click/paymentmethods/": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - c.Fatalf("device authentication missing") - } else if authorization == expiredAuth { - w.Header().Set("WWW-Authenticate", "Macaroon refresh_device_session=1") - w.WriteHeader(http.StatusUnauthorized) - } else { - c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) - io.WriteString(w, paymentMethodsJson) - } - case "/identity/api/v1/nonces": - io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": - authorization := r.Header.Get("X-Device-Authorization") - if authorization == "" { - io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) - deviceSessionRequested = true - } else { - c.Check(authorization, Equals, expiredAuth) - io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`) - refreshSessionRequested = true - } - default: - c.Fatalf("unexpected path %q", r.URL.Path) - } - })) - c.Assert(mockPurchasesServer, NotNil) - defer mockPurchasesServer.Close() - MyAppsDeviceNonceAPI = mockPurchasesServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockPurchasesServer.URL + "/identity/api/v1/sessions" - paymentMethodsURI, _ := url.Parse(mockPurchasesServer.URL + "/api/2.0/click/paymentmethods/") - - // make sure device session is not set - t.device.SessionMacaroon = "" - authContext := &testAuthContext{c: c, device: t.device, user: t.user} - cfg := Config{ - PaymentMethodsURI: paymentMethodsURI, - } - repo := New(&cfg, authContext) - c.Assert(repo, NotNil) - - result, err := repo.PaymentMethods(t.user) - c.Assert(err, IsNil) - c.Assert(result, NotNil) - c.Check(deviceSessionRequested, Equals, true) - c.Check(refreshSessionRequested, Equals, true) -} diff --git a/tests/lib/fakestore/store/store_test.go b/tests/lib/fakestore/store/store_test.go index d452766305..327fc79d48 100644 --- a/tests/lib/fakestore/store/store_test.go +++ b/tests/lib/fakestore/store/store_test.go @@ -1,5 +1,4 @@ // -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration /* * Copyright (C) 2014-2015 Canonical Ltd diff --git a/tests/main/create-key/successful_default.exp b/tests/main/create-key/successful_default.exp index 2e5b9ae2af..9f4e01ab9a 100644 --- a/tests/main/create-key/successful_default.exp +++ b/tests/main/create-key/successful_default.exp @@ -28,10 +28,11 @@ if {[lindex $status 3] != 0} { spawn snap export-key --account=developer default -expect "Enter passphrase: " -send "pass\n" - +# fun! +# gpg1 asks for a passphrase on the terminal no matter what +# gpg2 gets the passphrase via our fake pinentry expect { + "Enter passphrase: " {send "pass\n"; exp_continue} "account-id: developer" {} timeout { exit 1 } eof { exit 1 } diff --git a/tests/main/create-key/successful_non_default.exp b/tests/main/create-key/successful_non_default.exp index 5c7f815760..452f335a12 100644 --- a/tests/main/create-key/successful_non_default.exp +++ b/tests/main/create-key/successful_non_default.exp @@ -28,10 +28,11 @@ if {[lindex $status 3] != 0} { spawn snap export-key --account=developer another -expect "Enter passphrase: " -send "pass\n" - +# fun! +# gpg1 asks for a passphrase on the terminal no matter what +# gpg2 gets the passphrase via our fake pinentry expect { + "Enter passphrase: " {send "pass\n"; exp_continue} "account-id: developer" {} timeout { exit 1 } eof { exit 1 } diff --git a/tests/main/create-key/task.yaml b/tests/main/create-key/task.yaml index b596af5b09..a7975f6fa0 100644 --- a/tests/main/create-key/task.yaml +++ b/tests/main/create-key/task.yaml @@ -5,6 +5,33 @@ systems: [-ubuntu-core-16-64] restore: | rm -rf $HOME/.snap/gnupg +prepare: | + echo "setup fake gpg pinentry environment" + cat > /tmp/pinentry-fake <<'EOF' + #!/bin/sh + set -e + echo "OK Pleased to meet you" + while true; do + read line + case $line in + GETPIN) + echo "D pass" + echo "OK" + ;; + BYE) + exit 0 + ;; + *) + echo "OK I'm not very smart" + ;; + esac + done + EOF + chmod +x /tmp/pinentry-fake + mkdir -p /root/.snap/gnupg/ + chmod 0700 /root/.snap/gnupg/ + echo pinentry-program /tmp/pinentry-fake > /root/.snap/gnupg/gpg-agent.conf + execute: | echo "Checking passphrase mismatch error" expect -f passphrase_mismatch.exp diff --git a/tests/main/op-remove-retry/task.yaml b/tests/main/op-remove-retry/task.yaml index 75dcca5b2a..1c85e17451 100644 --- a/tests/main/op-remove-retry/task.yaml +++ b/tests/main/op-remove-retry/task.yaml @@ -41,4 +41,4 @@ execute: | # cleanup umount blocker systemctl stop unmount-blocker - wait_for_service unmount-blocker inactive + diff --git a/tests/main/writable-areas/task.yaml b/tests/main/writable-areas/task.yaml index 29a930c4d9..4c93e18a04 100644 --- a/tests/main/writable-areas/task.yaml +++ b/tests/main/writable-areas/task.yaml @@ -20,19 +20,9 @@ execute: | echo "Waiting for data writer service to finish..." unit="snap.data-writer.service.service" - while true; do - code=$(systemctl show -p ExecMainCode $unit | sed 's/.*=\([0-9]\+\)/\1/') - # The main code will be 0 until the service is no longer running - if [ $code -ne 0 ]; then - systemctl status $unit || true - status=$(systemctl show -p ExecMainStatus $unit | sed 's/.*=\([0-9]\+\)/\1/') - if [ $status -ne 0 ]; then - echo "Service exited $status" - exit 1 - fi - break - fi - sleep 1 + while ! systemctl status $unit | grep -q "Writing to SNAP_USER_DATA"; do + systemctl status $unit || true + sleep 1 done echo "Services can write to writable areas" diff --git a/integration-tests/manual-tests.md b/tests/manual-tests.md index 0217c30057..0217c30057 100644 --- a/integration-tests/manual-tests.md +++ b/tests/manual-tests.md |
