Merge lp:~chipaca/ubuntu-push/shuffle-signing-bits into lp:ubuntu-push

Proposed by John Lenton
Status: Superseded
Proposed branch: lp:~chipaca/ubuntu-push/shuffle-signing-bits
Merge into: lp:ubuntu-push
Diff against target: 959 lines (+525/-68)
18 files modified
client/client.go (+21/-1)
client/client_test.go (+36/-1)
client/session/session.go (+5/-17)
client/session/session_test.go (+6/-21)
debian/changelog (+14/-0)
debian/rules (+0/-1)
scripts/register (+43/-0)
scripts/unicast (+2/-0)
server/acceptance/acceptanceclient.go (+1/-1)
server/acceptance/cmd/acceptanceclient.go (+3/-1)
server/acceptance/suites/suite.go (+2/-2)
server/acceptance/suites/unicast.go (+11/-3)
server/api/handlers.go (+104/-8)
server/api/handlers_test.go (+193/-2)
server/store/inmemory.go (+26/-0)
server/store/inmemory_test.go (+44/-0)
server/store/store.go (+8/-0)
signing-helper/signing-helper.cpp (+6/-10)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/shuffle-signing-bits
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+222782@code.launchpad.net

This proposal has been superseded by a proposal from 2014-06-11.

Description of the change

Move the signing bits about a bit (up from session to client, for reuse from service).

To post a comment you must log in.
181. By John Lenton

merged trunk

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'client/client.go'
--- client/client.go 2014-05-21 10:03:18 +0000
+++ client/client.go 2014-06-11 11:43:11 +0000
@@ -28,6 +28,7 @@
28 "fmt"28 "fmt"
29 "io/ioutil"29 "io/ioutil"
30 "os"30 "os"
31 "os/exec"
31 "strings"32 "strings"
3233
33 "launchpad.net/go-dbus/v1"34 "launchpad.net/go-dbus/v1"
@@ -160,7 +161,26 @@
160 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),161 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),
161 PEM: client.pem,162 PEM: client.pem,
162 Info: info,163 Info: info,
163 AuthHelper: client.config.AuthHelper,164 AuthGetter: client.getAuthorization,
165 }
166}
167
168// getAuthorization gets the authorization blob to send to the server
169func (client *PushClient) getAuthorization() string {
170 client.log.Debugf("getting authorization")
171 // using a helper, for now at least
172 if len(client.config.AuthHelper) == 0 {
173 // do nothing if helper is unset or empty
174 return ""
175 }
176
177 auth, err := exec.Command(client.config.AuthHelper[0], client.config.AuthHelper[1:]...).Output()
178 if err != nil {
179 // For now we just log the error, as we don't want to block unauthorized users
180 client.log.Errorf("unable to get the authorization token from the account: %v", err)
181 return ""
182 } else {
183 return strings.TrimSpace(string(auth))
164 }184 }
165}185}
166186
167187
=== modified file 'client/client_test.go'
--- client/client_test.go 2014-05-20 13:56:37 +0000
+++ client/client_test.go 2014-06-11 11:43:11 +0000
@@ -272,7 +272,7 @@
272 ExpectAllRepairedTime: 30 * time.Minute,272 ExpectAllRepairedTime: 30 * time.Minute,
273 PEM: cli.pem,273 PEM: cli.pem,
274 Info: info,274 Info: info,
275 AuthHelper: []string{"auth", "helper"},275 AuthGetter: func() string { return "" },
276 }276 }
277 // sanity check that we are looking at all fields277 // sanity check that we are looking at all fields
278 vExpected := reflect.ValueOf(expected)278 vExpected := reflect.ValueOf(expected)
@@ -284,6 +284,11 @@
284 }284 }
285 // finally compare285 // finally compare
286 conf := cli.deriveSessionConfig(info)286 conf := cli.deriveSessionConfig(info)
287 // compare authGetter by string
288 c.Check(fmt.Sprintf("%v", conf.AuthGetter), Equals, fmt.Sprintf("%v", cli.getAuthorization))
289 // and set it to nil
290 conf.AuthGetter = nil
291 expected.AuthGetter = nil
287 c.Check(conf, DeepEquals, expected)292 c.Check(conf, DeepEquals, expected)
288}293}
289294
@@ -951,3 +956,33 @@
951 c.Assert(err, NotNil)956 c.Assert(err, NotNil)
952 c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$")957 c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$")
953}958}
959
960/*****************************************************************
961 getAuthorization() tests
962******************************************************************/
963
964func (cs *clientSuite) TestGetAuthorizationIgnoresErrors(c *C) {
965 cli := NewPushClient(cs.configPath, cs.leveldbPath)
966 cli.configure()
967 cli.config.AuthHelper = []string{"sh", "-c", "echo hello; false"}
968
969 c.Check(cli.getAuthorization(), Equals, "")
970}
971
972func (cs *clientSuite) TestGetAuthorizationGetsIt(c *C) {
973 cli := NewPushClient(cs.configPath, cs.leveldbPath)
974 cli.configure()
975 cli.config.AuthHelper = []string{"echo", "hello"}
976
977 c.Check(cli.getAuthorization(), Equals, "hello")
978}
979
980func (cs *clientSuite) TestGetAuthorizationWorksIfUnsetOrNil(c *C) {
981 cli := NewPushClient(cs.configPath, cs.leveldbPath)
982 cli.log = cs.log
983
984 c.Assert(cli.config, NotNil)
985 c.Check(cli.getAuthorization(), Equals, "")
986 cli.configure()
987 c.Check(cli.getAuthorization(), Equals, "")
988}
954989
=== modified file 'client/session/session.go'
--- client/session/session.go 2014-05-23 05:59:38 +0000
+++ client/session/session.go 2014-06-11 11:43:11 +0000
@@ -26,7 +26,6 @@
26 "fmt"26 "fmt"
27 "math/rand"27 "math/rand"
28 "net"28 "net"
29 "os/exec"
30 "strings"29 "strings"
31 "sync"30 "sync"
32 "sync/atomic"31 "sync/atomic"
@@ -87,7 +86,7 @@
87 ExpectAllRepairedTime time.Duration86 ExpectAllRepairedTime time.Duration
88 PEM []byte87 PEM []byte
89 Info map[string]interface{}88 Info map[string]interface{}
90 AuthHelper []string89 AuthGetter func() string
91}90}
9291
93// ClientSession holds a client<->server session and its configuration.92// ClientSession holds a client<->server session and its configuration.
@@ -244,21 +243,10 @@
244// addAuthorization gets the authorization blob to send to the server243// addAuthorization gets the authorization blob to send to the server
245// and adds it to the session.244// and adds it to the session.
246func (sess *ClientSession) addAuthorization() error {245func (sess *ClientSession) addAuthorization() error {
247 sess.Log.Debugf("adding authorization")246 if sess.AuthGetter != nil {
248 // using a helper, for now at least247 sess.Log.Debugf("adding authorization")
249 if len(sess.AuthHelper) == 0 {248 sess.auth = sess.AuthGetter()
250 // do nothing if helper is unset or empty249 }
251 return nil
252 }
253
254 auth, err := exec.Command(sess.AuthHelper[0], sess.AuthHelper[1:]...).Output()
255 if err != nil {
256 // For now we just log the error, as we don't want to block unauthorized users
257 sess.Log.Errorf("unable to get the authorization token from the account: %v", err)
258 } else {
259 sess.auth = strings.TrimSpace(string(auth))
260 }
261
262 return nil250 return nil
263}251}
264252
265253
=== modified file 'client/session/session_test.go'
--- client/session/session_test.go 2014-05-23 05:59:38 +0000
+++ client/session/session_test.go 2014-06-11 11:43:11 +0000
@@ -354,33 +354,18 @@
354354
355func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {355func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {
356 sess := &ClientSession{Log: cs.log}356 sess := &ClientSession{Log: cs.log}
357 sess.AuthHelper = []string{"echo", "some auth"}357 sess.AuthGetter = func() string { return "some auth" }
358 c.Assert(sess.auth, Equals, "")358 c.Assert(sess.auth, Equals, "")
359 err := sess.addAuthorization()359 err := sess.addAuthorization()
360 c.Assert(err, IsNil)360 c.Assert(err, IsNil)
361 c.Check(sess.auth, Equals, "some auth")361 c.Check(sess.auth, Equals, "some auth")
362}362}
363363
364func (cs *clientSessionSuite) TestAddAuthorizationIgnoresErrors(c *C) {364func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {
365 sess := &ClientSession{Log: cs.log}365 sess := &ClientSession{Log: cs.log}
366 sess.AuthHelper = []string{"sh", "-c", "echo hello; false"}366 sess.AuthGetter = nil
367367 c.Assert(sess.auth, Equals, "")
368 c.Assert(sess.auth, Equals, "")368 err := sess.addAuthorization()
369 err := sess.addAuthorization()
370 c.Assert(err, IsNil)
371 c.Check(sess.auth, Equals, "")
372}
373
374func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnsetOrNil(c *C) {
375 sess := &ClientSession{Log: cs.log}
376 sess.AuthHelper = nil
377 c.Assert(sess.auth, Equals, "")
378 err := sess.addAuthorization()
379 c.Assert(err, IsNil)
380 c.Check(sess.auth, Equals, "")
381
382 sess.AuthHelper = []string{}
383 err = sess.addAuthorization()
384 c.Assert(err, IsNil)369 c.Assert(err, IsNil)
385 c.Check(sess.auth, Equals, "")370 c.Check(sess.auth, Equals, "")
386}371}
387372
=== modified file 'debian/changelog'
--- debian/changelog 2014-06-05 09:42:22 +0000
+++ debian/changelog 2014-06-11 11:43:11 +0000
@@ -1,3 +1,17 @@
1ubuntu-push (0.31ubuntu1) UNRELEASED; urgency=medium
2
3 [ Samuele Pedroni ]
4 * Support registering tokens and sending notifications with a token
5 * Register script and scripts unicast support
6
7 [ Roberto Alsina ]
8 * Make signing-helper generate a HTTP header instead of a querystring.
9
10 [ John R. Lenton ]
11 * Move signing bits up from session to client, for reuse by service.
12
13 -- John R. Lenton <john.lenton@canonical.com> Fri, 06 Jun 2014 12:02:56 +0100
14
1ubuntu-push (0.3+14.10.20140605-0ubuntu1) utopic; urgency=medium15ubuntu-push (0.3+14.10.20140605-0ubuntu1) utopic; urgency=medium
216
3 [ John Lenton ]17 [ John Lenton ]
418
=== modified file 'debian/rules'
--- debian/rules 2014-05-02 12:42:27 +0000
+++ debian/rules 2014-06-11 11:43:11 +0000
@@ -5,7 +5,6 @@
5export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)5export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)
66
7override_dh_auto_build:7override_dh_auto_build:
8 cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1)
9 dh_auto_build --buildsystem=golang8 dh_auto_build --buildsystem=golang
10 (cd signing-helper && cmake . && make)9 (cd signing-helper && cmake . && make)
1110
1211
=== added file 'scripts/register'
--- scripts/register 1970-01-01 00:00:00 +0000
+++ scripts/register 2014-06-11 11:43:11 +0000
@@ -0,0 +1,43 @@
1#!/usr/bin/python3
2"""
3request a unicast registration
4"""
5import argparse
6import json
7import requests
8import subprocess
9import datetime
10import sys
11
12
13def main():
14 parser = argparse.ArgumentParser(description=__doc__)
15 parser.add_argument('deviceid', nargs=1)
16 parser.add_argument('appid', nargs=1)
17 parser.add_argument('-H', '--host',
18 help="host:port (default: %(default)s)",
19 default="localhost:8080")
20 parser.add_argument('--no-https', action='store_true', default=False)
21 parser.add_argument('--insecure', action='store_true', default=False,
22 help="don't check host/certs with https")
23 parser.add_argument('--auth_helper', default="")
24 args = parser.parse_args()
25 scheme = 'https'
26 if args.no_https:
27 scheme = 'http'
28 url = "%s://%s/register" % (scheme, args.host)
29 body = {
30 'deviceid': args.deviceid[0],
31 'appid': args.appid[0],
32 }
33 headers = {'Content-Type': 'application/json'}
34 if args.auth_helper:
35 auth = subprocess.check_output([args.auth_helper, url]).strip()
36 headers['Authorization'] = auth
37 r = requests.post(url, data=json.dumps(body), headers=headers,
38 verify=not args.insecure)
39 print(r.status_code)
40 print(r.text)
41
42if __name__ == '__main__':
43 main()
044
=== modified file 'scripts/unicast'
--- scripts/unicast 2014-05-29 14:55:19 +0000
+++ scripts/unicast 2014-06-11 11:43:11 +0000
@@ -52,6 +52,8 @@
52 userid, devid = reg.split(':', 1)52 userid, devid = reg.split(':', 1)
53 body['userid'] = userid53 body['userid'] = userid
54 body['deviceid'] = devid54 body['deviceid'] = devid
55 else:
56 body['token'] = reg
55 xauth = {}57 xauth = {}
56 if args.user and args.password:58 if args.user and args.password:
57 xauth = {'auth': requests.auth.HTTPBasicAuth(args.user, args.password)}59 xauth = {'auth': requests.auth.HTTPBasicAuth(args.user, args.password)}
5860
=== modified file 'server/acceptance/acceptanceclient.go'
--- server/acceptance/acceptanceclient.go 2014-05-23 12:30:32 +0000
+++ server/acceptance/acceptanceclient.go 2014-06-11 11:43:11 +0000
@@ -155,7 +155,7 @@
155 return err155 return err
156 }156 }
157 events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack)157 events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack)
158 case "connwarn":158 case "warn", "connwarn":
159 events <- fmt.Sprintf("%sconnwarn %s", sess.Prefix, recv.Reason)159 events <- fmt.Sprintf("%sconnwarn %s", sess.Prefix, recv.Reason)
160 }160 }
161 }161 }
162162
=== modified file 'server/acceptance/cmd/acceptanceclient.go'
--- server/acceptance/cmd/acceptanceclient.go 2014-05-21 07:58:26 +0000
+++ server/acceptance/cmd/acceptanceclient.go 2014-06-11 11:43:11 +0000
@@ -90,7 +90,9 @@
90 }90 }
91 }91 }
92 if len(cfg.AuthHelper) != 0 {92 if len(cfg.AuthHelper) != 0 {
93 auth, err := exec.Command(cfg.AuthHelper[0], cfg.AuthHelper[1:]...).Output()93 helperArgs := cfg.AuthHelper[1:]
94 helperArgs = append(helperArgs, "https://push.ubuntu.com/")
95 auth, err := exec.Command(cfg.AuthHelper[0], helperArgs...).Output()
94 if err != nil {96 if err != nil {
95 log.Fatalf("auth helper: %v", err)97 log.Fatalf("auth helper: %v", err)
96 }98 }
9799
=== modified file 'server/acceptance/suites/suite.go'
--- server/acceptance/suites/suite.go 2014-05-02 09:56:49 +0000
+++ server/acceptance/suites/suite.go 2014-06-11 11:43:11 +0000
@@ -89,7 +89,7 @@
89 // to kill the server process89 // to kill the server process
90 KillGroup map[string]func(os.Signal)90 KillGroup map[string]func(os.Signal)
91 // hook to adjust requests91 // hook to adjust requests
92 MassageRequest func(req *http.Request) *http.Request92 MassageRequest func(req *http.Request, message interface{}) *http.Request
93 // other state93 // other state
94 httpClient *http.Client94 httpClient *http.Client
95}95}
@@ -124,7 +124,7 @@
124 request.Header.Set("Content-Type", "application/json")124 request.Header.Set("Content-Type", "application/json")
125125
126 if s.MassageRequest != nil {126 if s.MassageRequest != nil {
127 request = s.MassageRequest(request)127 request = s.MassageRequest(request, message)
128 }128 }
129129
130 resp, err := s.httpClient.Do(request)130 resp, err := s.httpClient.Do(request)
131131
=== modified file 'server/acceptance/suites/unicast.go'
--- server/acceptance/suites/unicast.go 2014-05-15 16:41:54 +0000
+++ server/acceptance/suites/unicast.go 2014-06-11 11:43:11 +0000
@@ -40,11 +40,19 @@
40}40}
4141
42func (s *UnicastAcceptanceSuite) TestUnicastToConnected(c *C) {42func (s *UnicastAcceptanceSuite) TestUnicastToConnected(c *C) {
43 userId, auth := s.associatedAuth("DEV1")43 _, auth := s.associatedAuth("DEV1")
44 res, err := s.PostRequest("/register", &api.Registration{
45 DeviceId: "DEV1",
46 AppId: "app1",
47 })
48 c.Assert(err, IsNil)
49 c.Assert(res, Matches, ".*ok.*")
50 var reg map[string]interface{}
51 err = json.Unmarshal([]byte(res), &reg)
52 c.Assert(err, IsNil)
44 events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth)53 events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth)
45 got, err := s.PostRequest("/notify", &api.Unicast{54 got, err := s.PostRequest("/notify", &api.Unicast{
46 UserId: userId,55 Token: reg["token"].(string),
47 DeviceId: "DEV1",
48 AppId: "app1",56 AppId: "app1",
49 ExpireOn: future,57 ExpireOn: future,
50 Data: json.RawMessage(`{"a": 42}`),58 Data: json.RawMessage(`{"a": 42}`),
5159
=== modified file 'server/api/handlers.go'
--- server/api/handlers.go 2014-05-29 16:22:00 +0000
+++ server/api/handlers.go 2014-06-11 11:43:11 +0000
@@ -51,6 +51,8 @@
51 ioError = "io-error"51 ioError = "io-error"
52 invalidRequest = "invalid-request"52 invalidRequest = "invalid-request"
53 unknownChannel = "unknown-channel"53 unknownChannel = "unknown-channel"
54 unknownToken = "unknown-token"
55 unauthorized = "unauthorized"
54 unavailable = "unavailable"56 unavailable = "unavailable"
55 internalError = "internal"57 internalError = "internal"
56)58)
@@ -121,6 +123,11 @@
121 unknownChannel,123 unknownChannel,
122 "Unknown channel",124 "Unknown channel",
123 }125 }
126 ErrUnknownToken = &APIError{
127 http.StatusBadRequest,
128 unknownToken,
129 "Unknown token",
130 }
124 ErrUnknown = &APIError{131 ErrUnknown = &APIError{
125 http.StatusInternalServerError,132 http.StatusInternalServerError,
126 internalError,133 internalError,
@@ -136,16 +143,33 @@
136 unavailable,143 unavailable,
137 "Could not store notification",144 "Could not store notification",
138 }145 }
146 ErrCouldNotMakeToken = &APIError{
147 http.StatusServiceUnavailable,
148 unavailable,
149 "Could not make token",
150 }
151 ErrCouldNotResolveToken = &APIError{
152 http.StatusServiceUnavailable,
153 unavailable,
154 "Could not resolve token",
155 }
156 ErrUnauthorized = &APIError{
157 http.StatusUnauthorized,
158 unauthorized,
159 "Unauthorized",
160 }
139)161)
140162
141type castCommon struct {163type Registration struct {
164 DeviceId string `json:"deviceid"`
165 AppId string `json:"appid"`
142}166}
143167
144type Unicast struct {168type Unicast struct {
169 Token string `json:"token"`
145 UserId string `json:"userid"`170 UserId string `json:"userid"`
146 DeviceId string `json:"deviceid"`171 DeviceId string `json:"deviceid"`
147 AppId string `json:"appid"`172 AppId string `json:"appid"`
148 //Registration string `json:"registration"`
149 //CoalesceTag string `json:"coalesce_tag"`173 //CoalesceTag string `json:"coalesce_tag"`
150 ExpireOn string `json:"expire_on"`174 ExpireOn string `json:"expire_on"`
151 Data json.RawMessage `json:"data"`175 Data json.RawMessage `json:"data"`
@@ -183,15 +207,15 @@
183}207}
184208
185func checkRequestAsPost(request *http.Request, maxBodySize int64) *APIError {209func checkRequestAsPost(request *http.Request, maxBodySize int64) *APIError {
210 if request.Method != "POST" {
211 return ErrWrongRequestMethod
212 }
186 if err := checkContentLength(request, maxBodySize); err != nil {213 if err := checkContentLength(request, maxBodySize); err != nil {
187 return err214 return err
188 }215 }
189 if request.Header.Get("Content-Type") != JSONMediaType {216 if request.Header.Get("Content-Type") != JSONMediaType {
190 return ErrWrongContentType217 return ErrWrongContentType
191 }218 }
192 if request.Method != "POST" {
193 return ErrWrongRequestMethod
194 }
195 return nil219 return nil
196}220}
197221
@@ -325,7 +349,10 @@
325}349}
326350
327func checkUnicast(ucast *Unicast) (time.Time, *APIError) {351func checkUnicast(ucast *Unicast) (time.Time, *APIError) {
328 if ucast.UserId == "" || ucast.DeviceId == "" || ucast.AppId == "" {352 if ucast.AppId == "" {
353 return zeroTime, ErrMissingIdField
354 }
355 if ucast.Token == "" && (ucast.UserId == "" || ucast.DeviceId == "") {
329 return zeroTime, ErrMissingIdField356 return zeroTime, ErrMissingIdField
330 }357 }
331 return checkCastCommon(ucast.Data, ucast.ExpireOn)358 return checkCastCommon(ucast.Data, ucast.ExpireOn)
@@ -341,9 +368,21 @@
341 if apiErr != nil {368 if apiErr != nil {
342 return apiErr369 return apiErr
343 }370 }
344 chanId := store.UnicastInternalChannelId(ucast.UserId, ucast.DeviceId)371 chanId, err := sto.GetInternalChannelIdFromToken(ucast.Token, ucast.AppId, ucast.UserId, ucast.DeviceId)
372 if err != nil {
373 switch err {
374 case store.ErrUnknownToken:
375 return ErrUnknownToken
376 case store.ErrUnauthorized:
377 return ErrUnauthorized
378 default:
379 h.logger.Errorf("could not resolve token: %v", err)
380 return ErrCouldNotResolveToken
381 }
382 }
383
345 msgId := generateMsgId()384 msgId := generateMsgId()
346 err := sto.AppendToUnicastChannel(chanId, ucast.AppId, ucast.Data, msgId, expire)385 err = sto.AppendToUnicastChannel(chanId, ucast.AppId, ucast.Data, msgId, expire)
347 if err != nil {386 if err != nil {
348 h.logger.Errorf("could not store notification: %v", err)387 h.logger.Errorf("could not store notification: %v", err)
349 return ErrCouldNotStoreNotification388 return ErrCouldNotStoreNotification
@@ -378,6 +417,62 @@
378 fmt.Fprintf(writer, `{"ok":true}`)417 fmt.Fprintf(writer, `{"ok":true}`)
379}418}
380419
420type RegisterHandler struct {
421 *context
422}
423
424func checkRegister(reg *Registration) *APIError {
425 if reg.DeviceId == "" || reg.AppId == "" {
426 return ErrMissingIdField
427 }
428 return nil
429}
430
431func (h *RegisterHandler) doRegister(sto store.PendingStore, reg *Registration) (string, *APIError) {
432 apiErr := checkRegister(reg)
433 if apiErr != nil {
434 return "", apiErr
435 }
436 token, err := sto.Register(reg.DeviceId, reg.AppId)
437 if err != nil {
438 h.logger.Errorf("could not make a token: %v", err)
439 return "", ErrCouldNotMakeToken
440 }
441 return token, nil
442}
443
444func (h *RegisterHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
445 var apiErr *APIError
446 defer func() {
447 if apiErr != nil {
448 RespondError(writer, apiErr)
449 }
450 }()
451
452 reg := &Registration{}
453
454 sto, apiErr := h.prepare(writer, request, reg)
455 if apiErr != nil {
456 return
457 }
458 defer sto.Close()
459
460 token, apiErr := h.doRegister(sto, reg)
461 if apiErr != nil {
462 return
463 }
464
465 writer.Header().Set("Content-Type", "application/json")
466 res, err := json.Marshal(map[string]interface{}{
467 "ok": true,
468 "token": token,
469 })
470 if err != nil {
471 panic(fmt.Errorf("couldn't marshal our own response: %v", err))
472 }
473 writer.Write(res)
474}
475
381// MakeHandlersMux makes a handler that dispatches for the various API endpoints.476// MakeHandlersMux makes a handler that dispatches for the various API endpoints.
382func MakeHandlersMux(storeForRequest StoreForRequest, broker broker.BrokerSending, logger logger.Logger) *http.ServeMux {477func MakeHandlersMux(storeForRequest StoreForRequest, broker broker.BrokerSending, logger logger.Logger) *http.ServeMux {
383 ctx := &context{478 ctx := &context{
@@ -388,5 +483,6 @@
388 mux := http.NewServeMux()483 mux := http.NewServeMux()
389 mux.Handle("/broadcast", &BroadcastHandler{context: ctx})484 mux.Handle("/broadcast", &BroadcastHandler{context: ctx})
390 mux.Handle("/notify", &UnicastHandler{context: ctx})485 mux.Handle("/notify", &UnicastHandler{context: ctx})
486 mux.Handle("/register", &RegisterHandler{context: ctx})
391 return mux487 return mux
392}488}
393489
=== modified file 'server/api/handlers_test.go'
--- server/api/handlers_test.go 2014-05-01 18:58:16 +0000
+++ server/api/handlers_test.go 2014-06-11 11:43:11 +0000
@@ -192,6 +192,16 @@
192 intercept func(meth string, err error) error192 intercept func(meth string, err error) error
193}193}
194194
195func (isto *interceptInMemoryPendingStore) Register(appId, deviceId string) (string, error) {
196 token, err := isto.InMemoryPendingStore.Register(appId, deviceId)
197 return token, isto.intercept("Register", err)
198}
199
200func (isto *interceptInMemoryPendingStore) GetInternalChannelIdFromToken(token, appId, userId, deviceId string) (store.InternalChannelId, error) {
201 chanId, err := isto.InMemoryPendingStore.GetInternalChannelIdFromToken(token, appId, userId, deviceId)
202 return chanId, isto.intercept("GetInternalChannelIdFromToken", err)
203}
204
195func (isto *interceptInMemoryPendingStore) GetInternalChannelId(channel string) (store.InternalChannelId, error) {205func (isto *interceptInMemoryPendingStore) GetInternalChannelId(channel string) (store.InternalChannelId, error) {
196 chanId, err := isto.InMemoryPendingStore.GetInternalChannelId(channel)206 chanId, err := isto.InMemoryPendingStore.GetInternalChannelId(channel)
197 return chanId, isto.intercept("GetInternalChannelId", err)207 return chanId, isto.intercept("GetInternalChannelId", err)
@@ -262,6 +272,14 @@
262272
263 u = unicast()273 u = unicast()
264 u.UserId = ""274 u.UserId = ""
275 u.DeviceId = ""
276 u.Token = "TOKEN"
277 expire, apiErr = checkUnicast(u)
278 c.Assert(apiErr, IsNil)
279 c.Check(expire.Format(time.RFC3339), Equals, future)
280
281 u = unicast()
282 u.UserId = ""
265 expire, apiErr = checkUnicast(u)283 expire, apiErr = checkUnicast(u)
266 c.Check(apiErr, Equals, ErrMissingIdField)284 c.Check(apiErr, Equals, ErrMissingIdField)
267285
@@ -353,6 +371,40 @@
353 c.Check(s.testlog.Captured(), Equals, "ERROR could not store notification: fail\n")371 c.Check(s.testlog.Captured(), Equals, "ERROR could not store notification: fail\n")
354}372}
355373
374func (s *handlersSuite) TestDoUnicastFromTokenFailures(c *C) {
375 fail := errors.New("fail")
376 sto := &interceptInMemoryPendingStore{
377 store.NewInMemoryPendingStore(),
378 func(meth string, err error) error {
379 if meth == "GetInternalChannelIdFromToken" {
380 return fail
381 }
382 return err
383 },
384 }
385 ctx := &context{logger: s.testlog}
386 bh := &UnicastHandler{ctx}
387 u := &Unicast{
388 Token: "tok",
389 AppId: "app1",
390 ExpireOn: future,
391 Data: json.RawMessage(`{"a": 1}`),
392 }
393 apiErr := bh.doUnicast(sto, u)
394 c.Check(apiErr, Equals, ErrCouldNotResolveToken)
395 c.Check(s.testlog.Captured(), Equals, "ERROR could not resolve token: fail\n")
396 s.testlog.ResetCapture()
397
398 fail = store.ErrUnknownToken
399 apiErr = bh.doUnicast(sto, u)
400 c.Check(apiErr, Equals, ErrUnknownToken)
401 c.Check(s.testlog.Captured(), Equals, "")
402 fail = store.ErrUnauthorized
403 apiErr = bh.doUnicast(sto, u)
404 c.Check(apiErr, Equals, ErrUnauthorized)
405 c.Check(s.testlog.Captured(), Equals, "")
406}
407
356func newPostRequest(path string, message interface{}, server *httptest.Server) *http.Request {408func newPostRequest(path string, message interface{}, server *httptest.Server) *http.Request {
357 packedMessage, err := json.Marshal(message)409 packedMessage, err := json.Marshal(message)
358 if err != nil {410 if err != nil {
@@ -427,7 +479,7 @@
427 dest := make(map[string]bool)479 dest := make(map[string]bool)
428 err = json.Unmarshal(body, &dest)480 err = json.Unmarshal(body, &dest)
429 c.Assert(err, IsNil)481 c.Assert(err, IsNil)
430 c.Check(dest, DeepEquals, map[string]bool{"ok": true})482 c.Assert(dest, DeepEquals, map[string]bool{"ok": true})
431483
432 top, _, err := sto.GetChannelSnapshot(store.SystemInternalChannelId)484 top, _, err := sto.GetChannelSnapshot(store.SystemInternalChannelId)
433 c.Assert(err, IsNil)485 c.Assert(err, IsNil)
@@ -639,7 +691,7 @@
639 c.Check(response.Header.Get("Content-Type"), Equals, "application/json")691 c.Check(response.Header.Get("Content-Type"), Equals, "application/json")
640 body, err := getResponseBody(response)692 body, err := getResponseBody(response)
641 c.Assert(err, IsNil)693 c.Assert(err, IsNil)
642 c.Check(string(body), Matches, ".*ok.*")694 c.Assert(string(body), Matches, ".*ok.*")
643695
644 chanId := store.UnicastInternalChannelId("user2", "dev3")696 chanId := store.UnicastInternalChannelId("user2", "dev3")
645 c.Check(<-bsend.chanId, Equals, chanId)697 c.Check(<-bsend.chanId, Equals, chanId)
@@ -682,3 +734,142 @@
682 c.Assert(err, IsNil)734 c.Assert(err, IsNil)
683 checkError(c, response, ErrMissingIdField)735 checkError(c, response, ErrMissingIdField)
684}736}
737
738func (s *handlersSuite) TestCheckRegister(c *C) {
739 registration := func() *Registration {
740 return &Registration{
741 DeviceId: "DEV1",
742 AppId: "app1",
743 }
744 }
745 reg := registration()
746 apiErr := checkRegister(reg)
747 c.Assert(apiErr, IsNil)
748
749 reg = registration()
750 reg.AppId = ""
751 apiErr = checkRegister(reg)
752 c.Check(apiErr, Equals, ErrMissingIdField)
753
754 reg = registration()
755 reg.DeviceId = ""
756 apiErr = checkRegister(reg)
757 c.Check(apiErr, Equals, ErrMissingIdField)
758}
759
760func (s *handlersSuite) TestDoRegisterMissingIdField(c *C) {
761 sto := store.NewInMemoryPendingStore()
762 rh := &RegisterHandler{}
763 token, apiErr := rh.doRegister(sto, &Registration{})
764 c.Check(apiErr, Equals, ErrMissingIdField)
765 c.Check(token, Equals, "")
766}
767
768func (s *handlersSuite) TestDoRegisterCouldNotMakeToken(c *C) {
769 sto := &interceptInMemoryPendingStore{
770 store.NewInMemoryPendingStore(),
771 func(meth string, err error) error {
772 if meth == "Register" {
773 return errors.New("fail")
774 }
775 return err
776 },
777 }
778 ctx := &context{logger: s.testlog}
779 rh := &RegisterHandler{ctx}
780 _, apiErr := rh.doRegister(sto, &Registration{
781 DeviceId: "DEV1",
782 AppId: "app1",
783 })
784 c.Check(apiErr, Equals, ErrCouldNotMakeToken)
785 c.Check(s.testlog.Captured(), Equals, "ERROR could not make a token: fail\n")
786}
787
788func (s *handlersSuite) TestRespondsToRegisterAndUnicast(c *C) {
789 sto := store.NewInMemoryPendingStore()
790 stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
791 return sto, nil
792 }
793 bsend := testBrokerSending{make(chan store.InternalChannelId, 1)}
794 testServer := httptest.NewServer(MakeHandlersMux(stoForReq, bsend, nil))
795 defer testServer.Close()
796
797 request := newPostRequest("/register", &Registration{
798 DeviceId: "dev3",
799 AppId: "app2",
800 }, testServer)
801
802 response, err := s.client.Do(request)
803 c.Assert(err, IsNil)
804
805 c.Check(response.StatusCode, Equals, http.StatusOK)
806 c.Check(response.Header.Get("Content-Type"), Equals, "application/json")
807 body, err := getResponseBody(response)
808 c.Assert(err, IsNil)
809 c.Assert(string(body), Matches, ".*ok.*")
810 var reg map[string]interface{}
811 err = json.Unmarshal(body, &reg)
812 c.Assert(err, IsNil)
813
814 token, ok := reg["token"].(string)
815 c.Assert(ok, Equals, true)
816 c.Check(token, Not(Equals), nil)
817
818 payload := json.RawMessage(`{"foo":"bar"}`)
819
820 request = newPostRequest("/notify", &Unicast{
821 Token: token,
822 AppId: "app2",
823 ExpireOn: future,
824 Data: payload,
825 }, testServer)
826
827 response, err = s.client.Do(request)
828 c.Assert(err, IsNil)
829
830 c.Check(response.StatusCode, Equals, http.StatusOK)
831 c.Check(response.Header.Get("Content-Type"), Equals, "application/json")
832 body, err = getResponseBody(response)
833 c.Assert(err, IsNil)
834 c.Assert(string(body), Matches, ".*ok.*")
835
836 chanId := store.UnicastInternalChannelId("dev3", "dev3")
837 c.Check(<-bsend.chanId, Equals, chanId)
838 top, notifications, err := sto.GetChannelSnapshot(chanId)
839 c.Assert(err, IsNil)
840 c.Check(top, Equals, int64(0))
841 c.Check(notifications, HasLen, 1)
842}
843
844func (s *handlersSuite) TestCannotRegisterWithMissingFields(c *C) {
845 stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
846 return store.NewInMemoryPendingStore(), nil
847 }
848 ctx := &context{stoForReq, nil, nil}
849 testServer := httptest.NewServer(&RegisterHandler{ctx})
850 defer testServer.Close()
851
852 request := newPostRequest("/", &Registration{
853 DeviceId: "DEV1",
854 }, testServer)
855
856 response, err := s.client.Do(request)
857 c.Assert(err, IsNil)
858 checkError(c, response, ErrMissingIdField)
859}
860
861func (s *handlersSuite) TestCannotRegisterWithNonPOST(c *C) {
862 stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
863 return store.NewInMemoryPendingStore(), nil
864 }
865 ctx := &context{stoForReq, nil, nil}
866 testServer := httptest.NewServer(&RegisterHandler{ctx})
867 defer testServer.Close()
868
869 request, err := http.NewRequest("GET", testServer.URL, nil)
870 c.Assert(err, IsNil)
871
872 response, err := s.client.Do(request)
873 c.Assert(err, IsNil)
874 checkError(c, response, ErrWrongRequestMethod)
875}
685876
=== modified file 'server/store/inmemory.go'
--- server/store/inmemory.go 2014-05-02 15:10:18 +0000
+++ server/store/inmemory.go 2014-06-11 11:43:11 +0000
@@ -17,7 +17,10 @@
17package store17package store
1818
19import (19import (
20 "encoding/base64"
20 "encoding/json"21 "encoding/json"
22 "fmt"
23 "strings"
21 "sync"24 "sync"
22 "time"25 "time"
2326
@@ -44,6 +47,29 @@
44 }47 }
45}48}
4649
50func (sto *InMemoryPendingStore) Register(deviceId, appId string) (string, error) {
51 return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s::%s", appId, deviceId))), nil
52}
53
54func (sto *InMemoryPendingStore) GetInternalChannelIdFromToken(token, appId, userId, deviceId string) (InternalChannelId, error) {
55 if token != "" && appId != "" {
56 decoded, err := base64.StdEncoding.DecodeString(token)
57 if err != nil {
58 return "", ErrUnknownToken
59 }
60 token = string(decoded)
61 if !strings.HasPrefix(token, appId+"::") {
62 return "", ErrUnauthorized
63 }
64 deviceId := token[len(appId)+2:]
65 return UnicastInternalChannelId(deviceId, deviceId), nil
66 }
67 if userId != "" && deviceId != "" {
68 return UnicastInternalChannelId(userId, deviceId), nil
69 }
70 return "", ErrUnknownToken
71}
72
47func (sto *InMemoryPendingStore) GetInternalChannelId(name string) (InternalChannelId, error) {73func (sto *InMemoryPendingStore) GetInternalChannelId(name string) (InternalChannelId, error) {
48 if name == "system" {74 if name == "system" {
49 return SystemInternalChannelId, nil75 return SystemInternalChannelId, nil
5076
=== modified file 'server/store/inmemory_test.go'
--- server/store/inmemory_test.go 2014-04-30 17:44:56 +0000
+++ server/store/inmemory_test.go 2014-06-11 11:43:11 +0000
@@ -30,6 +30,50 @@
3030
31var _ = Suite(&inMemorySuite{})31var _ = Suite(&inMemorySuite{})
3232
33func (s *inMemorySuite) TestRegister(c *C) {
34 sto := NewInMemoryPendingStore()
35
36 tok1, err := sto.Register("DEV1", "app1")
37 c.Assert(err, IsNil)
38 tok2, err := sto.Register("DEV1", "app1")
39 c.Assert(err, IsNil)
40 c.Check(len(tok1), Not(Equals), 0)
41 c.Check(tok1, Equals, tok2)
42}
43
44func (s *inMemorySuite) TestGetInternalChannelIdFromToken(c *C) {
45 sto := NewInMemoryPendingStore()
46
47 tok1, err := sto.Register("DEV1", "app1")
48 c.Assert(err, IsNil)
49 chanId, err := sto.GetInternalChannelIdFromToken(tok1, "app1", "", "")
50 c.Assert(err, IsNil)
51 c.Check(chanId, Equals, UnicastInternalChannelId("DEV1", "DEV1"))
52}
53
54func (s *inMemorySuite) TestGetInternalChannelIdFromTokenFallback(c *C) {
55 sto := NewInMemoryPendingStore()
56
57 chanId, err := sto.GetInternalChannelIdFromToken("", "app1", "u1", "d1")
58 c.Assert(err, IsNil)
59 c.Check(chanId, Equals, UnicastInternalChannelId("u1", "d1"))
60}
61
62func (s *inMemorySuite) TestGetInternalChannelIdFromTokenErrors(c *C) {
63 sto := NewInMemoryPendingStore()
64 tok1, err := sto.Register("DEV1", "app1")
65 c.Assert(err, IsNil)
66
67 _, err = sto.GetInternalChannelIdFromToken(tok1, "app2", "", "")
68 c.Assert(err, Equals, ErrUnauthorized)
69
70 _, err = sto.GetInternalChannelIdFromToken("", "app2", "", "")
71 c.Assert(err, Equals, ErrUnknownToken)
72
73 _, err = sto.GetInternalChannelIdFromToken("****", "app2", "", "")
74 c.Assert(err, Equals, ErrUnknownToken)
75}
76
33func (s *inMemorySuite) TestGetInternalChannelId(c *C) {77func (s *inMemorySuite) TestGetInternalChannelId(c *C) {
34 sto := NewInMemoryPendingStore()78 sto := NewInMemoryPendingStore()
3579
3680
=== modified file 'server/store/store.go'
--- server/store/store.go 2014-05-02 15:10:18 +0000
+++ server/store/store.go 2014-06-11 11:43:11 +0000
@@ -52,6 +52,8 @@
52}52}
5353
54var ErrUnknownChannel = errors.New("unknown channel name")54var ErrUnknownChannel = errors.New("unknown channel name")
55var ErrUnknownToken = errors.New("unknown token")
56var ErrUnauthorized = errors.New("unauthorized")
55var ErrFull = errors.New("channel is full")57var ErrFull = errors.New("channel is full")
56var ErrExpected128BitsHexRepr = errors.New("expected 128 bits hex repr")58var ErrExpected128BitsHexRepr = errors.New("expected 128 bits hex repr")
5759
@@ -98,11 +100,17 @@
98100
99// PendingStore let store notifications into channels.101// PendingStore let store notifications into channels.
100type PendingStore interface {102type PendingStore interface {
103 // Register returns a token for a device id, application id pair.
104 Register(deviceId, appId string) (token string, err error)
101 // GetInternalChannelId returns the internal store id for a channel105 // GetInternalChannelId returns the internal store id for a channel
102 // given the name.106 // given the name.
103 GetInternalChannelId(name string) (InternalChannelId, error)107 GetInternalChannelId(name string) (InternalChannelId, error)
104 // AppendToChannel appends a notification to the channel.108 // AppendToChannel appends a notification to the channel.
105 AppendToChannel(chanId InternalChannelId, notification json.RawMessage, expiration time.Time) error109 AppendToChannel(chanId InternalChannelId, notification json.RawMessage, expiration time.Time) error
110 // GetInternalChannelIdFromToken returns the matching internal store
111 // id for a channel given a registered token and application id or
112 // directly a device id, user id pair.
113 GetInternalChannelIdFromToken(token, appId, userId, deviceId string) (InternalChannelId, error)
106 // AppendToUnicastChannel appends a notification to the unicast channel.114 // AppendToUnicastChannel appends a notification to the unicast channel.
107 // GetChannelSnapshot gets all the current notifications and115 // GetChannelSnapshot gets all the current notifications and
108 AppendToUnicastChannel(chanId InternalChannelId, appId string, notification json.RawMessage, msgId string, expiration time.Time) error116 AppendToUnicastChannel(chanId InternalChannelId, appId string, notification json.RawMessage, msgId string, expiration time.Time) error
109117
=== modified file 'signing-helper/signing-helper.cpp'
--- signing-helper/signing-helper.cpp 2014-05-01 10:24:23 +0000
+++ signing-helper/signing-helper.cpp 2014-06-11 11:43:11 +0000
@@ -33,6 +33,7 @@
33#include <QObject>33#include <QObject>
34#include <QString>34#include <QString>
35#include <QTimer>35#include <QTimer>
36#include <QUrlQuery>
3637
37#include "ssoservice.h"38#include "ssoservice.h"
38#include "token.h"39#include "token.h"
@@ -63,12 +64,8 @@
63 void SigningExample::handleCredentialsFound(Token token)64 void SigningExample::handleCredentialsFound(Token token)
64 {65 {
65 qDebug() << "Credentials found, signing url.";66 qDebug() << "Credentials found, signing url.";
6667 std::cout << token.signUrl(this->url, QStringLiteral("POST")).toStdString();
67 QString authHeader = token.signUrl(this->url, QStringLiteral("GET"), true);
68
69 std::cout << authHeader.toStdString() << "\n";
70 QCoreApplication::instance()->exit(0);68 QCoreApplication::instance()->exit(0);
71
72 }69 }
7370
74 void SigningExample::handleCredentialsNotFound()71 void SigningExample::handleCredentialsNotFound()
@@ -84,13 +81,12 @@
84int main(int argc, char *argv[])81int main(int argc, char *argv[])
85{82{
86 QCoreApplication a(argc, argv);83 QCoreApplication a(argc, argv);
8784 if (argc<2) {
88 UbuntuOne::SigningExample *example = new UbuntuOne::SigningExample(&a);85 return 2;
8986 }
87 UbuntuOne::SigningExample *example = new UbuntuOne::SigningExample(&a, argv[1]);
90 QObject::connect(example, SIGNAL(finished()), &a, SLOT(quit()));88 QObject::connect(example, SIGNAL(finished()), &a, SLOT(quit()));
91
92 QTimer::singleShot(0, example, SLOT(doExample()));89 QTimer::singleShot(0, example, SLOT(doExample()));
93
94 return a.exec();90 return a.exec();
95}91}
9692

Subscribers

People subscribed via source and target branches