OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library oauth2_test; | |
6 | |
7 import 'dart:io'; | |
8 import 'dart:json' as json; | |
9 import 'dart:uri'; | |
10 | |
11 import 'package:http/http.dart' as http; | |
12 import 'package:scheduled_test/scheduled_process.dart'; | |
13 import 'package:scheduled_test/scheduled_test.dart'; | |
14 import 'package:scheduled_test/scheduled_server.dart'; | |
15 | |
16 import '../../pub/io.dart'; | |
17 import '../../pub/utils.dart'; | |
18 import 'descriptor.dart' as d; | |
19 import 'test_pub.dart'; | |
20 | |
21 import 'dart:async'; | |
22 | |
23 main() { | |
24 setUp(() => d.validPackage.create()); | |
25 | |
26 integration('with no credentials.json, authenticates and saves ' | |
27 'credentials.json', () { | |
28 var server = new ScheduledServer(); | |
29 var pub = startPublish(server); | |
30 confirmPublish(pub); | |
31 authorizePub(pub, server); | |
32 | |
33 server.handle('GET', '/packages/versions/new.json', (request) { | |
34 expect(request.headers.value('authorization'), | |
35 equals('Bearer access token')); | |
36 | |
37 request.response.close(); | |
38 }); | |
39 | |
40 // After we give pub an invalid response, it should crash. We wait for it to | |
41 // do so rather than killing it so it'll write out the credentials file. | |
42 pub.shouldExit(1); | |
43 | |
44 d.credentialsFile(server, 'access token').validate(); | |
45 }); | |
46 | |
47 integration('with a pre-existing credentials.json does not authenticate', () { | |
48 var server = new ScheduledServer(); | |
49 d.credentialsFile(server, 'access token').create(); | |
50 var pub = startPublish(server); | |
51 confirmPublish(pub); | |
52 | |
53 server.handle('GET', '/packages/versions/new.json', (request) { | |
54 expect(request.headers.value('authorization'), | |
55 equals('Bearer access token')); | |
56 | |
57 request.response.close(); | |
58 }); | |
59 | |
60 pub.kill(); | |
61 }); | |
62 | |
63 integration('with an expired credentials.json, refreshes and saves the ' | |
64 'refreshed access token to credentials.json', () { | |
65 var server = new ScheduledServer(); | |
66 d.credentialsFile(server, 'access token', | |
67 refreshToken: 'refresh token', | |
68 expiration: new DateTime.now().subtract(new Duration(hours: 1))) | |
69 .create(); | |
70 | |
71 var pub = startPublish(server); | |
72 confirmPublish(pub); | |
73 | |
74 server.handle('POST', '/token', (request) { | |
75 return new ByteStream(request).toBytes().then((bytes) { | |
76 var body = new String.fromCharCodes(bytes); | |
77 expect(body, matches( | |
78 new RegExp(r'(^|&)refresh_token=refresh\+token(&|$)'))); | |
79 | |
80 request.response.headers.contentType = | |
81 new ContentType("application", "json"); | |
82 request.response.write(json.stringify({ | |
83 "access_token": "new access token", | |
84 "token_type": "bearer" | |
85 })); | |
86 request.response.close(); | |
87 }); | |
88 }); | |
89 | |
90 server.handle('GET', '/packages/versions/new.json', (request) { | |
91 expect(request.headers.value('authorization'), | |
92 equals('Bearer new access token')); | |
93 | |
94 request.response.close(); | |
95 }); | |
96 | |
97 pub.shouldExit(); | |
98 | |
99 d.credentialsFile(server, 'new access token', refreshToken: 'refresh token') | |
100 .validate(); | |
101 }); | |
102 | |
103 integration('with an expired credentials.json without a refresh token, ' | |
104 'authenticates again and saves credentials.json', () { | |
105 var server = new ScheduledServer(); | |
106 d.credentialsFile(server, 'access token', | |
107 expiration: new DateTime.now().subtract(new Duration(hours: 1))) | |
108 .create(); | |
109 | |
110 var pub = startPublish(server); | |
111 confirmPublish(pub); | |
112 | |
113 expect(pub.nextErrLine(), completion(equals("Pub's authorization to upload " | |
114 "packages has expired and can't be automatically refreshed."))); | |
115 authorizePub(pub, server, "new access token"); | |
116 | |
117 server.handle('GET', '/packages/versions/new.json', (request) { | |
118 expect(request.headers.value('authorization'), | |
119 equals('Bearer new access token')); | |
120 | |
121 request.response.close(); | |
122 }); | |
123 | |
124 // After we give pub an invalid response, it should crash. We wait for it to | |
125 // do so rather than killing it so it'll write out the credentials file. | |
126 pub.shouldExit(1); | |
127 | |
128 d.credentialsFile(server, 'new access token').validate(); | |
129 }); | |
130 | |
131 integration('with a malformed credentials.json, authenticates again and ' | |
132 'saves credentials.json', () { | |
133 var server = new ScheduledServer(); | |
134 d.dir(cachePath, [ | |
135 d.file('credentials.json', '{bad json') | |
136 ]).create(); | |
137 | |
138 var pub = startPublish(server); | |
139 confirmPublish(pub); | |
140 authorizePub(pub, server, "new access token"); | |
141 | |
142 server.handle('GET', '/packages/versions/new.json', (request) { | |
143 expect(request.headers.value('authorization'), | |
144 equals('Bearer new access token')); | |
145 | |
146 request.response.close(); | |
147 }); | |
148 | |
149 // After we give pub an invalid response, it should crash. We wait for it to | |
150 // do so rather than killing it so it'll write out the credentials file. | |
151 pub.shouldExit(1); | |
152 | |
153 d.credentialsFile(server, 'new access token').validate(); | |
154 }); | |
155 | |
156 // Regression test for issue 8849. | |
157 integration('with a server-rejected refresh token, authenticates again and ' | |
158 'saves credentials.json', () { | |
159 var server = new ScheduledServer(); | |
160 d.credentialsFile(server, 'access token', | |
161 refreshToken: 'bad refresh token', | |
162 expiration: new DateTime.now().subtract(new Duration(hours: 1))) | |
163 .create(); | |
164 | |
165 var pub = startPublish(server); | |
166 confirmPublish(pub); | |
167 | |
168 server.handle('POST', '/token', (request) { | |
169 return new ByteStream(request).toBytes().then((bytes) { | |
170 var response = request.response; | |
171 response.statusCode = 400; | |
172 response.reasonPhrase = 'Bad request'; | |
173 response.headers.contentType = new ContentType("application", "json"); | |
174 response.write(json.stringify({"error": "invalid_request"})); | |
175 response.close(); | |
176 }); | |
177 }); | |
178 | |
179 authorizePub(pub, server, 'new access token'); | |
180 | |
181 server.handle('GET', '/packages/versions/new.json', (request) { | |
182 expect(request.headers.value('authorization'), | |
183 equals('Bearer new access token')); | |
184 | |
185 request.response.close(); | |
186 }); | |
187 | |
188 pub.kill(); | |
189 }); | |
190 | |
191 integration('with server-rejected credentials, authenticates again and saves ' | |
192 'credentials.json', () { | |
193 var server = new ScheduledServer(); | |
194 d.credentialsFile(server, 'access token').create(); | |
195 var pub = startPublish(server); | |
196 | |
197 confirmPublish(pub); | |
198 | |
199 server.handle('GET', '/packages/versions/new.json', (request) { | |
200 var response = request.response; | |
201 response.statusCode = 401; | |
202 response.headers.set('www-authenticate', 'Bearer error="invalid_token",' | |
203 ' error_description="your token sucks"'); | |
204 response.write(json.stringify({ | |
205 'error': {'message': 'your token sucks'} | |
206 })); | |
207 response.close(); | |
208 }); | |
209 | |
210 expect(pub.nextErrLine(), completion(equals('OAuth2 authorization failed ' | |
211 '(your token sucks).'))); | |
212 // TODO(rnystrom): The confirm line is run together with this one because | |
213 // in normal usage, the user will have entered a newline on stdin which | |
214 // gets echoed to the terminal. Do something better here? | |
215 expect(pub.nextLine(), completion(equals( | |
216 'Looks great! Are you ready to upload your package (y/n)? ' | |
217 'Pub needs your authorization to upload packages on your behalf.'))); | |
218 pub.kill(); | |
219 }); | |
220 } | |
221 | |
222 void authorizePub(ScheduledProcess pub, ScheduledServer server, | |
223 [String accessToken="access token"]) { | |
224 // TODO(rnystrom): The confirm line is run together with this one because | |
225 // in normal usage, the user will have entered a newline on stdin which | |
226 // gets echoed to the terminal. Do something better here? | |
227 expect(pub.nextLine(), completion(equals( | |
228 'Looks great! Are you ready to upload your package (y/n)? ' | |
229 'Pub needs your authorization to upload packages on your behalf.'))); | |
230 | |
231 expect(pub.nextLine().then((line) { | |
232 var match = new RegExp(r'[?&]redirect_uri=([0-9a-zA-Z%+-]+)[$&]') | |
233 .firstMatch(line); | |
234 expect(match, isNotNull); | |
235 | |
236 var redirectUrl = Uri.parse(decodeUriComponent(match.group(1))); | |
237 redirectUrl = addQueryParameters(redirectUrl, {'code': 'access code'}); | |
238 return (new http.Request('GET', redirectUrl)..followRedirects = false) | |
239 .send(); | |
240 }).then((response) { | |
241 expect(response.headers['location'], | |
242 equals('http://pub.dartlang.org/authorized')); | |
243 }), completes); | |
244 | |
245 handleAccessTokenRequest(server, accessToken); | |
246 } | |
247 | |
248 void handleAccessTokenRequest(ScheduledServer server, String accessToken) { | |
249 server.handle('POST', '/token', (request) { | |
250 return new ByteStream(request).toBytes().then((bytes) { | |
251 var body = new String.fromCharCodes(bytes); | |
252 expect(body, matches(new RegExp(r'(^|&)code=access\+code(&|$)'))); | |
253 | |
254 request.response.headers.contentType = | |
255 new ContentType("application", "json"); | |
256 request.response.write(json.stringify({ | |
257 "access_token": accessToken, | |
258 "token_type": "bearer" | |
259 })); | |
260 request.response.close(); | |
261 }); | |
262 }); | |
263 } | |
264 | |
OLD | NEW |