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