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
1=== modified file 'client/client.go'
2--- client/client.go 2014-05-21 10:03:18 +0000
3+++ client/client.go 2014-06-11 11:43:11 +0000
4@@ -28,6 +28,7 @@
5 "fmt"
6 "io/ioutil"
7 "os"
8+ "os/exec"
9 "strings"
10
11 "launchpad.net/go-dbus/v1"
12@@ -160,7 +161,26 @@
13 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),
14 PEM: client.pem,
15 Info: info,
16- AuthHelper: client.config.AuthHelper,
17+ AuthGetter: client.getAuthorization,
18+ }
19+}
20+
21+// getAuthorization gets the authorization blob to send to the server
22+func (client *PushClient) getAuthorization() string {
23+ client.log.Debugf("getting authorization")
24+ // using a helper, for now at least
25+ if len(client.config.AuthHelper) == 0 {
26+ // do nothing if helper is unset or empty
27+ return ""
28+ }
29+
30+ auth, err := exec.Command(client.config.AuthHelper[0], client.config.AuthHelper[1:]...).Output()
31+ if err != nil {
32+ // For now we just log the error, as we don't want to block unauthorized users
33+ client.log.Errorf("unable to get the authorization token from the account: %v", err)
34+ return ""
35+ } else {
36+ return strings.TrimSpace(string(auth))
37 }
38 }
39
40
41=== modified file 'client/client_test.go'
42--- client/client_test.go 2014-05-20 13:56:37 +0000
43+++ client/client_test.go 2014-06-11 11:43:11 +0000
44@@ -272,7 +272,7 @@
45 ExpectAllRepairedTime: 30 * time.Minute,
46 PEM: cli.pem,
47 Info: info,
48- AuthHelper: []string{"auth", "helper"},
49+ AuthGetter: func() string { return "" },
50 }
51 // sanity check that we are looking at all fields
52 vExpected := reflect.ValueOf(expected)
53@@ -284,6 +284,11 @@
54 }
55 // finally compare
56 conf := cli.deriveSessionConfig(info)
57+ // compare authGetter by string
58+ c.Check(fmt.Sprintf("%v", conf.AuthGetter), Equals, fmt.Sprintf("%v", cli.getAuthorization))
59+ // and set it to nil
60+ conf.AuthGetter = nil
61+ expected.AuthGetter = nil
62 c.Check(conf, DeepEquals, expected)
63 }
64
65@@ -951,3 +956,33 @@
66 c.Assert(err, NotNil)
67 c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$")
68 }
69+
70+/*****************************************************************
71+ getAuthorization() tests
72+******************************************************************/
73+
74+func (cs *clientSuite) TestGetAuthorizationIgnoresErrors(c *C) {
75+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
76+ cli.configure()
77+ cli.config.AuthHelper = []string{"sh", "-c", "echo hello; false"}
78+
79+ c.Check(cli.getAuthorization(), Equals, "")
80+}
81+
82+func (cs *clientSuite) TestGetAuthorizationGetsIt(c *C) {
83+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
84+ cli.configure()
85+ cli.config.AuthHelper = []string{"echo", "hello"}
86+
87+ c.Check(cli.getAuthorization(), Equals, "hello")
88+}
89+
90+func (cs *clientSuite) TestGetAuthorizationWorksIfUnsetOrNil(c *C) {
91+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
92+ cli.log = cs.log
93+
94+ c.Assert(cli.config, NotNil)
95+ c.Check(cli.getAuthorization(), Equals, "")
96+ cli.configure()
97+ c.Check(cli.getAuthorization(), Equals, "")
98+}
99
100=== modified file 'client/session/session.go'
101--- client/session/session.go 2014-05-23 05:59:38 +0000
102+++ client/session/session.go 2014-06-11 11:43:11 +0000
103@@ -26,7 +26,6 @@
104 "fmt"
105 "math/rand"
106 "net"
107- "os/exec"
108 "strings"
109 "sync"
110 "sync/atomic"
111@@ -87,7 +86,7 @@
112 ExpectAllRepairedTime time.Duration
113 PEM []byte
114 Info map[string]interface{}
115- AuthHelper []string
116+ AuthGetter func() string
117 }
118
119 // ClientSession holds a client<->server session and its configuration.
120@@ -244,21 +243,10 @@
121 // addAuthorization gets the authorization blob to send to the server
122 // and adds it to the session.
123 func (sess *ClientSession) addAuthorization() error {
124- sess.Log.Debugf("adding authorization")
125- // using a helper, for now at least
126- if len(sess.AuthHelper) == 0 {
127- // do nothing if helper is unset or empty
128- return nil
129- }
130-
131- auth, err := exec.Command(sess.AuthHelper[0], sess.AuthHelper[1:]...).Output()
132- if err != nil {
133- // For now we just log the error, as we don't want to block unauthorized users
134- sess.Log.Errorf("unable to get the authorization token from the account: %v", err)
135- } else {
136- sess.auth = strings.TrimSpace(string(auth))
137- }
138-
139+ if sess.AuthGetter != nil {
140+ sess.Log.Debugf("adding authorization")
141+ sess.auth = sess.AuthGetter()
142+ }
143 return nil
144 }
145
146
147=== modified file 'client/session/session_test.go'
148--- client/session/session_test.go 2014-05-23 05:59:38 +0000
149+++ client/session/session_test.go 2014-06-11 11:43:11 +0000
150@@ -354,33 +354,18 @@
151
152 func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {
153 sess := &ClientSession{Log: cs.log}
154- sess.AuthHelper = []string{"echo", "some auth"}
155+ sess.AuthGetter = func() string { return "some auth" }
156 c.Assert(sess.auth, Equals, "")
157 err := sess.addAuthorization()
158 c.Assert(err, IsNil)
159 c.Check(sess.auth, Equals, "some auth")
160 }
161
162-func (cs *clientSessionSuite) TestAddAuthorizationIgnoresErrors(c *C) {
163- sess := &ClientSession{Log: cs.log}
164- sess.AuthHelper = []string{"sh", "-c", "echo hello; false"}
165-
166- c.Assert(sess.auth, Equals, "")
167- err := sess.addAuthorization()
168- c.Assert(err, IsNil)
169- c.Check(sess.auth, Equals, "")
170-}
171-
172-func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnsetOrNil(c *C) {
173- sess := &ClientSession{Log: cs.log}
174- sess.AuthHelper = nil
175- c.Assert(sess.auth, Equals, "")
176- err := sess.addAuthorization()
177- c.Assert(err, IsNil)
178- c.Check(sess.auth, Equals, "")
179-
180- sess.AuthHelper = []string{}
181- err = sess.addAuthorization()
182+func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {
183+ sess := &ClientSession{Log: cs.log}
184+ sess.AuthGetter = nil
185+ c.Assert(sess.auth, Equals, "")
186+ err := sess.addAuthorization()
187 c.Assert(err, IsNil)
188 c.Check(sess.auth, Equals, "")
189 }
190
191=== modified file 'debian/changelog'
192--- debian/changelog 2014-06-05 09:42:22 +0000
193+++ debian/changelog 2014-06-11 11:43:11 +0000
194@@ -1,3 +1,17 @@
195+ubuntu-push (0.31ubuntu1) UNRELEASED; urgency=medium
196+
197+ [ Samuele Pedroni ]
198+ * Support registering tokens and sending notifications with a token
199+ * Register script and scripts unicast support
200+
201+ [ Roberto Alsina ]
202+ * Make signing-helper generate a HTTP header instead of a querystring.
203+
204+ [ John R. Lenton ]
205+ * Move signing bits up from session to client, for reuse by service.
206+
207+ -- John R. Lenton <john.lenton@canonical.com> Fri, 06 Jun 2014 12:02:56 +0100
208+
209 ubuntu-push (0.3+14.10.20140605-0ubuntu1) utopic; urgency=medium
210
211 [ John Lenton ]
212
213=== modified file 'debian/rules'
214--- debian/rules 2014-05-02 12:42:27 +0000
215+++ debian/rules 2014-06-11 11:43:11 +0000
216@@ -5,7 +5,6 @@
217 export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)
218
219 override_dh_auto_build:
220- cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1)
221 dh_auto_build --buildsystem=golang
222 (cd signing-helper && cmake . && make)
223
224
225=== added file 'scripts/register'
226--- scripts/register 1970-01-01 00:00:00 +0000
227+++ scripts/register 2014-06-11 11:43:11 +0000
228@@ -0,0 +1,43 @@
229+#!/usr/bin/python3
230+"""
231+request a unicast registration
232+"""
233+import argparse
234+import json
235+import requests
236+import subprocess
237+import datetime
238+import sys
239+
240+
241+def main():
242+ parser = argparse.ArgumentParser(description=__doc__)
243+ parser.add_argument('deviceid', nargs=1)
244+ parser.add_argument('appid', nargs=1)
245+ parser.add_argument('-H', '--host',
246+ help="host:port (default: %(default)s)",
247+ default="localhost:8080")
248+ parser.add_argument('--no-https', action='store_true', default=False)
249+ parser.add_argument('--insecure', action='store_true', default=False,
250+ help="don't check host/certs with https")
251+ parser.add_argument('--auth_helper', default="")
252+ args = parser.parse_args()
253+ scheme = 'https'
254+ if args.no_https:
255+ scheme = 'http'
256+ url = "%s://%s/register" % (scheme, args.host)
257+ body = {
258+ 'deviceid': args.deviceid[0],
259+ 'appid': args.appid[0],
260+ }
261+ headers = {'Content-Type': 'application/json'}
262+ if args.auth_helper:
263+ auth = subprocess.check_output([args.auth_helper, url]).strip()
264+ headers['Authorization'] = auth
265+ r = requests.post(url, data=json.dumps(body), headers=headers,
266+ verify=not args.insecure)
267+ print(r.status_code)
268+ print(r.text)
269+
270+if __name__ == '__main__':
271+ main()
272
273=== modified file 'scripts/unicast'
274--- scripts/unicast 2014-05-29 14:55:19 +0000
275+++ scripts/unicast 2014-06-11 11:43:11 +0000
276@@ -52,6 +52,8 @@
277 userid, devid = reg.split(':', 1)
278 body['userid'] = userid
279 body['deviceid'] = devid
280+ else:
281+ body['token'] = reg
282 xauth = {}
283 if args.user and args.password:
284 xauth = {'auth': requests.auth.HTTPBasicAuth(args.user, args.password)}
285
286=== modified file 'server/acceptance/acceptanceclient.go'
287--- server/acceptance/acceptanceclient.go 2014-05-23 12:30:32 +0000
288+++ server/acceptance/acceptanceclient.go 2014-06-11 11:43:11 +0000
289@@ -155,7 +155,7 @@
290 return err
291 }
292 events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack)
293- case "connwarn":
294+ case "warn", "connwarn":
295 events <- fmt.Sprintf("%sconnwarn %s", sess.Prefix, recv.Reason)
296 }
297 }
298
299=== modified file 'server/acceptance/cmd/acceptanceclient.go'
300--- server/acceptance/cmd/acceptanceclient.go 2014-05-21 07:58:26 +0000
301+++ server/acceptance/cmd/acceptanceclient.go 2014-06-11 11:43:11 +0000
302@@ -90,7 +90,9 @@
303 }
304 }
305 if len(cfg.AuthHelper) != 0 {
306- auth, err := exec.Command(cfg.AuthHelper[0], cfg.AuthHelper[1:]...).Output()
307+ helperArgs := cfg.AuthHelper[1:]
308+ helperArgs = append(helperArgs, "https://push.ubuntu.com/")
309+ auth, err := exec.Command(cfg.AuthHelper[0], helperArgs...).Output()
310 if err != nil {
311 log.Fatalf("auth helper: %v", err)
312 }
313
314=== modified file 'server/acceptance/suites/suite.go'
315--- server/acceptance/suites/suite.go 2014-05-02 09:56:49 +0000
316+++ server/acceptance/suites/suite.go 2014-06-11 11:43:11 +0000
317@@ -89,7 +89,7 @@
318 // to kill the server process
319 KillGroup map[string]func(os.Signal)
320 // hook to adjust requests
321- MassageRequest func(req *http.Request) *http.Request
322+ MassageRequest func(req *http.Request, message interface{}) *http.Request
323 // other state
324 httpClient *http.Client
325 }
326@@ -124,7 +124,7 @@
327 request.Header.Set("Content-Type", "application/json")
328
329 if s.MassageRequest != nil {
330- request = s.MassageRequest(request)
331+ request = s.MassageRequest(request, message)
332 }
333
334 resp, err := s.httpClient.Do(request)
335
336=== modified file 'server/acceptance/suites/unicast.go'
337--- server/acceptance/suites/unicast.go 2014-05-15 16:41:54 +0000
338+++ server/acceptance/suites/unicast.go 2014-06-11 11:43:11 +0000
339@@ -40,11 +40,19 @@
340 }
341
342 func (s *UnicastAcceptanceSuite) TestUnicastToConnected(c *C) {
343- userId, auth := s.associatedAuth("DEV1")
344+ _, auth := s.associatedAuth("DEV1")
345+ res, err := s.PostRequest("/register", &api.Registration{
346+ DeviceId: "DEV1",
347+ AppId: "app1",
348+ })
349+ c.Assert(err, IsNil)
350+ c.Assert(res, Matches, ".*ok.*")
351+ var reg map[string]interface{}
352+ err = json.Unmarshal([]byte(res), &reg)
353+ c.Assert(err, IsNil)
354 events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth)
355 got, err := s.PostRequest("/notify", &api.Unicast{
356- UserId: userId,
357- DeviceId: "DEV1",
358+ Token: reg["token"].(string),
359 AppId: "app1",
360 ExpireOn: future,
361 Data: json.RawMessage(`{"a": 42}`),
362
363=== modified file 'server/api/handlers.go'
364--- server/api/handlers.go 2014-05-29 16:22:00 +0000
365+++ server/api/handlers.go 2014-06-11 11:43:11 +0000
366@@ -51,6 +51,8 @@
367 ioError = "io-error"
368 invalidRequest = "invalid-request"
369 unknownChannel = "unknown-channel"
370+ unknownToken = "unknown-token"
371+ unauthorized = "unauthorized"
372 unavailable = "unavailable"
373 internalError = "internal"
374 )
375@@ -121,6 +123,11 @@
376 unknownChannel,
377 "Unknown channel",
378 }
379+ ErrUnknownToken = &APIError{
380+ http.StatusBadRequest,
381+ unknownToken,
382+ "Unknown token",
383+ }
384 ErrUnknown = &APIError{
385 http.StatusInternalServerError,
386 internalError,
387@@ -136,16 +143,33 @@
388 unavailable,
389 "Could not store notification",
390 }
391+ ErrCouldNotMakeToken = &APIError{
392+ http.StatusServiceUnavailable,
393+ unavailable,
394+ "Could not make token",
395+ }
396+ ErrCouldNotResolveToken = &APIError{
397+ http.StatusServiceUnavailable,
398+ unavailable,
399+ "Could not resolve token",
400+ }
401+ ErrUnauthorized = &APIError{
402+ http.StatusUnauthorized,
403+ unauthorized,
404+ "Unauthorized",
405+ }
406 )
407
408-type castCommon struct {
409+type Registration struct {
410+ DeviceId string `json:"deviceid"`
411+ AppId string `json:"appid"`
412 }
413
414 type Unicast struct {
415+ Token string `json:"token"`
416 UserId string `json:"userid"`
417 DeviceId string `json:"deviceid"`
418 AppId string `json:"appid"`
419- //Registration string `json:"registration"`
420 //CoalesceTag string `json:"coalesce_tag"`
421 ExpireOn string `json:"expire_on"`
422 Data json.RawMessage `json:"data"`
423@@ -183,15 +207,15 @@
424 }
425
426 func checkRequestAsPost(request *http.Request, maxBodySize int64) *APIError {
427+ if request.Method != "POST" {
428+ return ErrWrongRequestMethod
429+ }
430 if err := checkContentLength(request, maxBodySize); err != nil {
431 return err
432 }
433 if request.Header.Get("Content-Type") != JSONMediaType {
434 return ErrWrongContentType
435 }
436- if request.Method != "POST" {
437- return ErrWrongRequestMethod
438- }
439 return nil
440 }
441
442@@ -325,7 +349,10 @@
443 }
444
445 func checkUnicast(ucast *Unicast) (time.Time, *APIError) {
446- if ucast.UserId == "" || ucast.DeviceId == "" || ucast.AppId == "" {
447+ if ucast.AppId == "" {
448+ return zeroTime, ErrMissingIdField
449+ }
450+ if ucast.Token == "" && (ucast.UserId == "" || ucast.DeviceId == "") {
451 return zeroTime, ErrMissingIdField
452 }
453 return checkCastCommon(ucast.Data, ucast.ExpireOn)
454@@ -341,9 +368,21 @@
455 if apiErr != nil {
456 return apiErr
457 }
458- chanId := store.UnicastInternalChannelId(ucast.UserId, ucast.DeviceId)
459+ chanId, err := sto.GetInternalChannelIdFromToken(ucast.Token, ucast.AppId, ucast.UserId, ucast.DeviceId)
460+ if err != nil {
461+ switch err {
462+ case store.ErrUnknownToken:
463+ return ErrUnknownToken
464+ case store.ErrUnauthorized:
465+ return ErrUnauthorized
466+ default:
467+ h.logger.Errorf("could not resolve token: %v", err)
468+ return ErrCouldNotResolveToken
469+ }
470+ }
471+
472 msgId := generateMsgId()
473- err := sto.AppendToUnicastChannel(chanId, ucast.AppId, ucast.Data, msgId, expire)
474+ err = sto.AppendToUnicastChannel(chanId, ucast.AppId, ucast.Data, msgId, expire)
475 if err != nil {
476 h.logger.Errorf("could not store notification: %v", err)
477 return ErrCouldNotStoreNotification
478@@ -378,6 +417,62 @@
479 fmt.Fprintf(writer, `{"ok":true}`)
480 }
481
482+type RegisterHandler struct {
483+ *context
484+}
485+
486+func checkRegister(reg *Registration) *APIError {
487+ if reg.DeviceId == "" || reg.AppId == "" {
488+ return ErrMissingIdField
489+ }
490+ return nil
491+}
492+
493+func (h *RegisterHandler) doRegister(sto store.PendingStore, reg *Registration) (string, *APIError) {
494+ apiErr := checkRegister(reg)
495+ if apiErr != nil {
496+ return "", apiErr
497+ }
498+ token, err := sto.Register(reg.DeviceId, reg.AppId)
499+ if err != nil {
500+ h.logger.Errorf("could not make a token: %v", err)
501+ return "", ErrCouldNotMakeToken
502+ }
503+ return token, nil
504+}
505+
506+func (h *RegisterHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
507+ var apiErr *APIError
508+ defer func() {
509+ if apiErr != nil {
510+ RespondError(writer, apiErr)
511+ }
512+ }()
513+
514+ reg := &Registration{}
515+
516+ sto, apiErr := h.prepare(writer, request, reg)
517+ if apiErr != nil {
518+ return
519+ }
520+ defer sto.Close()
521+
522+ token, apiErr := h.doRegister(sto, reg)
523+ if apiErr != nil {
524+ return
525+ }
526+
527+ writer.Header().Set("Content-Type", "application/json")
528+ res, err := json.Marshal(map[string]interface{}{
529+ "ok": true,
530+ "token": token,
531+ })
532+ if err != nil {
533+ panic(fmt.Errorf("couldn't marshal our own response: %v", err))
534+ }
535+ writer.Write(res)
536+}
537+
538 // MakeHandlersMux makes a handler that dispatches for the various API endpoints.
539 func MakeHandlersMux(storeForRequest StoreForRequest, broker broker.BrokerSending, logger logger.Logger) *http.ServeMux {
540 ctx := &context{
541@@ -388,5 +483,6 @@
542 mux := http.NewServeMux()
543 mux.Handle("/broadcast", &BroadcastHandler{context: ctx})
544 mux.Handle("/notify", &UnicastHandler{context: ctx})
545+ mux.Handle("/register", &RegisterHandler{context: ctx})
546 return mux
547 }
548
549=== modified file 'server/api/handlers_test.go'
550--- server/api/handlers_test.go 2014-05-01 18:58:16 +0000
551+++ server/api/handlers_test.go 2014-06-11 11:43:11 +0000
552@@ -192,6 +192,16 @@
553 intercept func(meth string, err error) error
554 }
555
556+func (isto *interceptInMemoryPendingStore) Register(appId, deviceId string) (string, error) {
557+ token, err := isto.InMemoryPendingStore.Register(appId, deviceId)
558+ return token, isto.intercept("Register", err)
559+}
560+
561+func (isto *interceptInMemoryPendingStore) GetInternalChannelIdFromToken(token, appId, userId, deviceId string) (store.InternalChannelId, error) {
562+ chanId, err := isto.InMemoryPendingStore.GetInternalChannelIdFromToken(token, appId, userId, deviceId)
563+ return chanId, isto.intercept("GetInternalChannelIdFromToken", err)
564+}
565+
566 func (isto *interceptInMemoryPendingStore) GetInternalChannelId(channel string) (store.InternalChannelId, error) {
567 chanId, err := isto.InMemoryPendingStore.GetInternalChannelId(channel)
568 return chanId, isto.intercept("GetInternalChannelId", err)
569@@ -262,6 +272,14 @@
570
571 u = unicast()
572 u.UserId = ""
573+ u.DeviceId = ""
574+ u.Token = "TOKEN"
575+ expire, apiErr = checkUnicast(u)
576+ c.Assert(apiErr, IsNil)
577+ c.Check(expire.Format(time.RFC3339), Equals, future)
578+
579+ u = unicast()
580+ u.UserId = ""
581 expire, apiErr = checkUnicast(u)
582 c.Check(apiErr, Equals, ErrMissingIdField)
583
584@@ -353,6 +371,40 @@
585 c.Check(s.testlog.Captured(), Equals, "ERROR could not store notification: fail\n")
586 }
587
588+func (s *handlersSuite) TestDoUnicastFromTokenFailures(c *C) {
589+ fail := errors.New("fail")
590+ sto := &interceptInMemoryPendingStore{
591+ store.NewInMemoryPendingStore(),
592+ func(meth string, err error) error {
593+ if meth == "GetInternalChannelIdFromToken" {
594+ return fail
595+ }
596+ return err
597+ },
598+ }
599+ ctx := &context{logger: s.testlog}
600+ bh := &UnicastHandler{ctx}
601+ u := &Unicast{
602+ Token: "tok",
603+ AppId: "app1",
604+ ExpireOn: future,
605+ Data: json.RawMessage(`{"a": 1}`),
606+ }
607+ apiErr := bh.doUnicast(sto, u)
608+ c.Check(apiErr, Equals, ErrCouldNotResolveToken)
609+ c.Check(s.testlog.Captured(), Equals, "ERROR could not resolve token: fail\n")
610+ s.testlog.ResetCapture()
611+
612+ fail = store.ErrUnknownToken
613+ apiErr = bh.doUnicast(sto, u)
614+ c.Check(apiErr, Equals, ErrUnknownToken)
615+ c.Check(s.testlog.Captured(), Equals, "")
616+ fail = store.ErrUnauthorized
617+ apiErr = bh.doUnicast(sto, u)
618+ c.Check(apiErr, Equals, ErrUnauthorized)
619+ c.Check(s.testlog.Captured(), Equals, "")
620+}
621+
622 func newPostRequest(path string, message interface{}, server *httptest.Server) *http.Request {
623 packedMessage, err := json.Marshal(message)
624 if err != nil {
625@@ -427,7 +479,7 @@
626 dest := make(map[string]bool)
627 err = json.Unmarshal(body, &dest)
628 c.Assert(err, IsNil)
629- c.Check(dest, DeepEquals, map[string]bool{"ok": true})
630+ c.Assert(dest, DeepEquals, map[string]bool{"ok": true})
631
632 top, _, err := sto.GetChannelSnapshot(store.SystemInternalChannelId)
633 c.Assert(err, IsNil)
634@@ -639,7 +691,7 @@
635 c.Check(response.Header.Get("Content-Type"), Equals, "application/json")
636 body, err := getResponseBody(response)
637 c.Assert(err, IsNil)
638- c.Check(string(body), Matches, ".*ok.*")
639+ c.Assert(string(body), Matches, ".*ok.*")
640
641 chanId := store.UnicastInternalChannelId("user2", "dev3")
642 c.Check(<-bsend.chanId, Equals, chanId)
643@@ -682,3 +734,142 @@
644 c.Assert(err, IsNil)
645 checkError(c, response, ErrMissingIdField)
646 }
647+
648+func (s *handlersSuite) TestCheckRegister(c *C) {
649+ registration := func() *Registration {
650+ return &Registration{
651+ DeviceId: "DEV1",
652+ AppId: "app1",
653+ }
654+ }
655+ reg := registration()
656+ apiErr := checkRegister(reg)
657+ c.Assert(apiErr, IsNil)
658+
659+ reg = registration()
660+ reg.AppId = ""
661+ apiErr = checkRegister(reg)
662+ c.Check(apiErr, Equals, ErrMissingIdField)
663+
664+ reg = registration()
665+ reg.DeviceId = ""
666+ apiErr = checkRegister(reg)
667+ c.Check(apiErr, Equals, ErrMissingIdField)
668+}
669+
670+func (s *handlersSuite) TestDoRegisterMissingIdField(c *C) {
671+ sto := store.NewInMemoryPendingStore()
672+ rh := &RegisterHandler{}
673+ token, apiErr := rh.doRegister(sto, &Registration{})
674+ c.Check(apiErr, Equals, ErrMissingIdField)
675+ c.Check(token, Equals, "")
676+}
677+
678+func (s *handlersSuite) TestDoRegisterCouldNotMakeToken(c *C) {
679+ sto := &interceptInMemoryPendingStore{
680+ store.NewInMemoryPendingStore(),
681+ func(meth string, err error) error {
682+ if meth == "Register" {
683+ return errors.New("fail")
684+ }
685+ return err
686+ },
687+ }
688+ ctx := &context{logger: s.testlog}
689+ rh := &RegisterHandler{ctx}
690+ _, apiErr := rh.doRegister(sto, &Registration{
691+ DeviceId: "DEV1",
692+ AppId: "app1",
693+ })
694+ c.Check(apiErr, Equals, ErrCouldNotMakeToken)
695+ c.Check(s.testlog.Captured(), Equals, "ERROR could not make a token: fail\n")
696+}
697+
698+func (s *handlersSuite) TestRespondsToRegisterAndUnicast(c *C) {
699+ sto := store.NewInMemoryPendingStore()
700+ stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
701+ return sto, nil
702+ }
703+ bsend := testBrokerSending{make(chan store.InternalChannelId, 1)}
704+ testServer := httptest.NewServer(MakeHandlersMux(stoForReq, bsend, nil))
705+ defer testServer.Close()
706+
707+ request := newPostRequest("/register", &Registration{
708+ DeviceId: "dev3",
709+ AppId: "app2",
710+ }, testServer)
711+
712+ response, err := s.client.Do(request)
713+ c.Assert(err, IsNil)
714+
715+ c.Check(response.StatusCode, Equals, http.StatusOK)
716+ c.Check(response.Header.Get("Content-Type"), Equals, "application/json")
717+ body, err := getResponseBody(response)
718+ c.Assert(err, IsNil)
719+ c.Assert(string(body), Matches, ".*ok.*")
720+ var reg map[string]interface{}
721+ err = json.Unmarshal(body, &reg)
722+ c.Assert(err, IsNil)
723+
724+ token, ok := reg["token"].(string)
725+ c.Assert(ok, Equals, true)
726+ c.Check(token, Not(Equals), nil)
727+
728+ payload := json.RawMessage(`{"foo":"bar"}`)
729+
730+ request = newPostRequest("/notify", &Unicast{
731+ Token: token,
732+ AppId: "app2",
733+ ExpireOn: future,
734+ Data: payload,
735+ }, testServer)
736+
737+ response, err = s.client.Do(request)
738+ c.Assert(err, IsNil)
739+
740+ c.Check(response.StatusCode, Equals, http.StatusOK)
741+ c.Check(response.Header.Get("Content-Type"), Equals, "application/json")
742+ body, err = getResponseBody(response)
743+ c.Assert(err, IsNil)
744+ c.Assert(string(body), Matches, ".*ok.*")
745+
746+ chanId := store.UnicastInternalChannelId("dev3", "dev3")
747+ c.Check(<-bsend.chanId, Equals, chanId)
748+ top, notifications, err := sto.GetChannelSnapshot(chanId)
749+ c.Assert(err, IsNil)
750+ c.Check(top, Equals, int64(0))
751+ c.Check(notifications, HasLen, 1)
752+}
753+
754+func (s *handlersSuite) TestCannotRegisterWithMissingFields(c *C) {
755+ stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
756+ return store.NewInMemoryPendingStore(), nil
757+ }
758+ ctx := &context{stoForReq, nil, nil}
759+ testServer := httptest.NewServer(&RegisterHandler{ctx})
760+ defer testServer.Close()
761+
762+ request := newPostRequest("/", &Registration{
763+ DeviceId: "DEV1",
764+ }, testServer)
765+
766+ response, err := s.client.Do(request)
767+ c.Assert(err, IsNil)
768+ checkError(c, response, ErrMissingIdField)
769+}
770+
771+func (s *handlersSuite) TestCannotRegisterWithNonPOST(c *C) {
772+ stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
773+ return store.NewInMemoryPendingStore(), nil
774+ }
775+ ctx := &context{stoForReq, nil, nil}
776+ testServer := httptest.NewServer(&RegisterHandler{ctx})
777+ defer testServer.Close()
778+
779+ request, err := http.NewRequest("GET", testServer.URL, nil)
780+ c.Assert(err, IsNil)
781+
782+ response, err := s.client.Do(request)
783+ c.Assert(err, IsNil)
784+ checkError(c, response, ErrWrongRequestMethod)
785+}
786
787=== modified file 'server/store/inmemory.go'
788--- server/store/inmemory.go 2014-05-02 15:10:18 +0000
789+++ server/store/inmemory.go 2014-06-11 11:43:11 +0000
790@@ -17,7 +17,10 @@
791 package store
792
793 import (
794+ "encoding/base64"
795 "encoding/json"
796+ "fmt"
797+ "strings"
798 "sync"
799 "time"
800
801@@ -44,6 +47,29 @@
802 }
803 }
804
805+func (sto *InMemoryPendingStore) Register(deviceId, appId string) (string, error) {
806+ return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s::%s", appId, deviceId))), nil
807+}
808+
809+func (sto *InMemoryPendingStore) GetInternalChannelIdFromToken(token, appId, userId, deviceId string) (InternalChannelId, error) {
810+ if token != "" && appId != "" {
811+ decoded, err := base64.StdEncoding.DecodeString(token)
812+ if err != nil {
813+ return "", ErrUnknownToken
814+ }
815+ token = string(decoded)
816+ if !strings.HasPrefix(token, appId+"::") {
817+ return "", ErrUnauthorized
818+ }
819+ deviceId := token[len(appId)+2:]
820+ return UnicastInternalChannelId(deviceId, deviceId), nil
821+ }
822+ if userId != "" && deviceId != "" {
823+ return UnicastInternalChannelId(userId, deviceId), nil
824+ }
825+ return "", ErrUnknownToken
826+}
827+
828 func (sto *InMemoryPendingStore) GetInternalChannelId(name string) (InternalChannelId, error) {
829 if name == "system" {
830 return SystemInternalChannelId, nil
831
832=== modified file 'server/store/inmemory_test.go'
833--- server/store/inmemory_test.go 2014-04-30 17:44:56 +0000
834+++ server/store/inmemory_test.go 2014-06-11 11:43:11 +0000
835@@ -30,6 +30,50 @@
836
837 var _ = Suite(&inMemorySuite{})
838
839+func (s *inMemorySuite) TestRegister(c *C) {
840+ sto := NewInMemoryPendingStore()
841+
842+ tok1, err := sto.Register("DEV1", "app1")
843+ c.Assert(err, IsNil)
844+ tok2, err := sto.Register("DEV1", "app1")
845+ c.Assert(err, IsNil)
846+ c.Check(len(tok1), Not(Equals), 0)
847+ c.Check(tok1, Equals, tok2)
848+}
849+
850+func (s *inMemorySuite) TestGetInternalChannelIdFromToken(c *C) {
851+ sto := NewInMemoryPendingStore()
852+
853+ tok1, err := sto.Register("DEV1", "app1")
854+ c.Assert(err, IsNil)
855+ chanId, err := sto.GetInternalChannelIdFromToken(tok1, "app1", "", "")
856+ c.Assert(err, IsNil)
857+ c.Check(chanId, Equals, UnicastInternalChannelId("DEV1", "DEV1"))
858+}
859+
860+func (s *inMemorySuite) TestGetInternalChannelIdFromTokenFallback(c *C) {
861+ sto := NewInMemoryPendingStore()
862+
863+ chanId, err := sto.GetInternalChannelIdFromToken("", "app1", "u1", "d1")
864+ c.Assert(err, IsNil)
865+ c.Check(chanId, Equals, UnicastInternalChannelId("u1", "d1"))
866+}
867+
868+func (s *inMemorySuite) TestGetInternalChannelIdFromTokenErrors(c *C) {
869+ sto := NewInMemoryPendingStore()
870+ tok1, err := sto.Register("DEV1", "app1")
871+ c.Assert(err, IsNil)
872+
873+ _, err = sto.GetInternalChannelIdFromToken(tok1, "app2", "", "")
874+ c.Assert(err, Equals, ErrUnauthorized)
875+
876+ _, err = sto.GetInternalChannelIdFromToken("", "app2", "", "")
877+ c.Assert(err, Equals, ErrUnknownToken)
878+
879+ _, err = sto.GetInternalChannelIdFromToken("****", "app2", "", "")
880+ c.Assert(err, Equals, ErrUnknownToken)
881+}
882+
883 func (s *inMemorySuite) TestGetInternalChannelId(c *C) {
884 sto := NewInMemoryPendingStore()
885
886
887=== modified file 'server/store/store.go'
888--- server/store/store.go 2014-05-02 15:10:18 +0000
889+++ server/store/store.go 2014-06-11 11:43:11 +0000
890@@ -52,6 +52,8 @@
891 }
892
893 var ErrUnknownChannel = errors.New("unknown channel name")
894+var ErrUnknownToken = errors.New("unknown token")
895+var ErrUnauthorized = errors.New("unauthorized")
896 var ErrFull = errors.New("channel is full")
897 var ErrExpected128BitsHexRepr = errors.New("expected 128 bits hex repr")
898
899@@ -98,11 +100,17 @@
900
901 // PendingStore let store notifications into channels.
902 type PendingStore interface {
903+ // Register returns a token for a device id, application id pair.
904+ Register(deviceId, appId string) (token string, err error)
905 // GetInternalChannelId returns the internal store id for a channel
906 // given the name.
907 GetInternalChannelId(name string) (InternalChannelId, error)
908 // AppendToChannel appends a notification to the channel.
909 AppendToChannel(chanId InternalChannelId, notification json.RawMessage, expiration time.Time) error
910+ // GetInternalChannelIdFromToken returns the matching internal store
911+ // id for a channel given a registered token and application id or
912+ // directly a device id, user id pair.
913+ GetInternalChannelIdFromToken(token, appId, userId, deviceId string) (InternalChannelId, error)
914 // AppendToUnicastChannel appends a notification to the unicast channel.
915 // GetChannelSnapshot gets all the current notifications and
916 AppendToUnicastChannel(chanId InternalChannelId, appId string, notification json.RawMessage, msgId string, expiration time.Time) error
917
918=== modified file 'signing-helper/signing-helper.cpp'
919--- signing-helper/signing-helper.cpp 2014-05-01 10:24:23 +0000
920+++ signing-helper/signing-helper.cpp 2014-06-11 11:43:11 +0000
921@@ -33,6 +33,7 @@
922 #include <QObject>
923 #include <QString>
924 #include <QTimer>
925+#include <QUrlQuery>
926
927 #include "ssoservice.h"
928 #include "token.h"
929@@ -63,12 +64,8 @@
930 void SigningExample::handleCredentialsFound(Token token)
931 {
932 qDebug() << "Credentials found, signing url.";
933-
934- QString authHeader = token.signUrl(this->url, QStringLiteral("GET"), true);
935-
936- std::cout << authHeader.toStdString() << "\n";
937+ std::cout << token.signUrl(this->url, QStringLiteral("POST")).toStdString();
938 QCoreApplication::instance()->exit(0);
939-
940 }
941
942 void SigningExample::handleCredentialsNotFound()
943@@ -84,13 +81,12 @@
944 int main(int argc, char *argv[])
945 {
946 QCoreApplication a(argc, argv);
947-
948- UbuntuOne::SigningExample *example = new UbuntuOne::SigningExample(&a);
949-
950+ if (argc<2) {
951+ return 2;
952+ }
953+ UbuntuOne::SigningExample *example = new UbuntuOne::SigningExample(&a, argv[1]);
954 QObject::connect(example, SIGNAL(finished()), &a, SLOT(quit()));
955-
956 QTimer::singleShot(0, example, SLOT(doExample()));
957-
958 return a.exec();
959 }
960

Subscribers

People subscribed via source and target branches