OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library oauth2; | 5 library oauth2; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 import 'dart:uri'; | 9 import 'dart:uri'; |
10 | 10 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
44 | 44 |
45 /// The OAuth2 scopes that the pub client needs. Currently the client only needs | 45 /// The OAuth2 scopes that the pub client needs. Currently the client only needs |
46 /// the user's email so that the server can verify their identity. | 46 /// the user's email so that the server can verify their identity. |
47 final _scopes = ['https://www.googleapis.com/auth/userinfo.email']; | 47 final _scopes = ['https://www.googleapis.com/auth/userinfo.email']; |
48 | 48 |
49 /// An in-memory cache of the user's OAuth2 credentials. This should always be | 49 /// An in-memory cache of the user's OAuth2 credentials. This should always be |
50 /// the same as the credentials file stored in the system cache. | 50 /// the same as the credentials file stored in the system cache. |
51 Credentials _credentials; | 51 Credentials _credentials; |
52 | 52 |
53 /// Delete the cached credentials, if they exist. | 53 /// Delete the cached credentials, if they exist. |
54 Future clearCredentials(SystemCache cache) { | 54 void clearCredentials(SystemCache cache) { |
55 _credentials = null; | 55 _credentials = null; |
56 var credentialsFile = _credentialsFile(cache); | 56 var credentialsFile = _credentialsFile(cache); |
57 return fileExists(credentialsFile).then((exists) { | 57 if (!fileExists(credentialsFile)) return; |
58 if (exists) return deleteFile(credentialsFile); | 58 |
59 }); | 59 deleteFile(credentialsFile); |
60 } | 60 } |
61 | 61 |
62 /// Asynchronously passes an OAuth2 [Client] to [fn], and closes the client when | 62 /// Asynchronously passes an OAuth2 [Client] to [fn], and closes the client when |
63 /// the [Future] returned by [fn] completes. | 63 /// the [Future] returned by [fn] completes. |
64 /// | 64 /// |
65 /// This takes care of loading and saving the client's credentials, as well as | 65 /// This takes care of loading and saving the client's credentials, as well as |
66 /// prompting the user for their authorization. It will also re-authorize and | 66 /// prompting the user for their authorization. It will also re-authorize and |
67 /// re-run [fn] if a recoverable authorization error is detected. | 67 /// re-run [fn] if a recoverable authorization error is detected. |
68 Future withClient(SystemCache cache, Future fn(Client client)) { | 68 Future withClient(SystemCache cache, Future fn(Client client)) { |
69 return _getClient(cache).then((client) { | 69 return _getClient(cache).then((client) { |
70 var completer = new Completer(); | 70 var completer = new Completer(); |
71 return fn(client).whenComplete(() { | 71 return fn(client).whenComplete(() { |
72 client.close(); | 72 client.close(); |
73 // Be sure to save the credentials even when an error happens. | 73 // Be sure to save the credentials even when an error happens. |
74 return _saveCredentials(cache, client.credentials); | 74 _saveCredentials(cache, client.credentials); |
75 }); | 75 }); |
76 }).catchError((asyncError) { | 76 }).catchError((asyncError) { |
77 if (asyncError.error is ExpirationException) { | 77 if (asyncError.error is ExpirationException) { |
78 log.error("Pub's authorization to upload packages has expired and " | 78 log.error("Pub's authorization to upload packages has expired and " |
79 "can't be automatically refreshed."); | 79 "can't be automatically refreshed."); |
80 return withClient(cache, fn); | 80 return withClient(cache, fn); |
81 } else if (asyncError.error is AuthorizationException) { | 81 } else if (asyncError.error is AuthorizationException) { |
82 var message = "OAuth2 authorization failed"; | 82 var message = "OAuth2 authorization failed"; |
83 if (asyncError.error.description != null) { | 83 if (asyncError.error.description != null) { |
84 message = "$message (${asyncError.error.description})"; | 84 message = "$message (${asyncError.error.description})"; |
85 } | 85 } |
86 log.error("$message."); | 86 log.error("$message."); |
87 return clearCredentials(cache).then((_) => withClient(cache, fn)); | 87 clearCredentials(cache); |
| 88 return withClient(cache, fn); |
88 } else { | 89 } else { |
89 throw asyncError; | 90 throw asyncError; |
90 } | 91 } |
91 }); | 92 }); |
92 } | 93 } |
93 | 94 |
94 /// Gets a new OAuth2 client. If saved credentials are available, those are | 95 /// Gets a new OAuth2 client. If saved credentials are available, those are |
95 /// used; otherwise, the user is prompted to authorize the pub client. | 96 /// used; otherwise, the user is prompted to authorize the pub client. |
96 Future<Client> _getClient(SystemCache cache) { | 97 Future<Client> _getClient(SystemCache cache) { |
97 return _loadCredentials(cache).then((credentials) { | 98 return defer(() { |
| 99 var credentials = _loadCredentials(cache); |
98 if (credentials == null) return _authorize(); | 100 if (credentials == null) return _authorize(); |
99 return new Client(_identifier, _secret, credentials, | 101 |
100 httpClient: httpClient); | 102 var client = new Client(_identifier, _secret, credentials, |
101 }).then((client) { | 103 httpClient: curlClient); |
102 return _saveCredentials(cache, client.credentials).then((_) => client); | 104 _saveCredentials(cache, client.credentials); |
| 105 return client; |
103 }); | 106 }); |
104 } | 107 } |
105 | 108 |
106 /// Loads the user's OAuth2 credentials from the in-memory cache or the | 109 /// Loads the user's OAuth2 credentials from the in-memory cache or the |
107 /// filesystem if possible. If the credentials can't be loaded for any reason, | 110 /// filesystem if possible. If the credentials can't be loaded for any reason, |
108 /// the returned [Future] will complete to null. | 111 /// the returned [Future] will complete to null. |
109 Future<Credentials> _loadCredentials(SystemCache cache) { | 112 Credentials _loadCredentials(SystemCache cache) { |
110 log.fine('Loading OAuth2 credentials.'); | 113 log.fine('Loading OAuth2 credentials.'); |
111 | 114 |
112 if (_credentials != null) { | 115 try { |
113 log.fine('Using already-loaded credentials.'); | 116 if (_credentials != null) return _credentials; |
114 return new Future.immediate(_credentials); | |
115 } | |
116 | 117 |
117 var path = _credentialsFile(cache); | 118 var path = _credentialsFile(cache); |
118 return fileExists(path).then((credentialsExist) { | 119 if (!fileExists(path)) return; |
119 if (!credentialsExist) { | 120 |
120 log.fine('No credentials found at $path.'); | 121 var credentials = new Credentials.fromJson(readTextFile(path)); |
121 return; | 122 if (credentials.isExpired && !credentials.canRefresh) { |
| 123 log.error("Pub's authorization to upload packages has expired and " |
| 124 "can't be automatically refreshed."); |
| 125 return null; // null means re-authorize. |
122 } | 126 } |
123 | 127 |
124 return readTextFile(_credentialsFile(cache)).then((credentialsJson) { | 128 return credentials; |
125 var credentials = new Credentials.fromJson(credentialsJson); | 129 } catch (e) { |
126 if (credentials.isExpired && !credentials.canRefresh) { | |
127 log.error("Pub's authorization to upload packages has expired and " | |
128 "can't be automatically refreshed."); | |
129 return null; // null means re-authorize | |
130 } | |
131 | |
132 return credentials; | |
133 }); | |
134 }).catchError((e) { | |
135 log.error('Warning: could not load the saved OAuth2 credentials: $e\n' | 130 log.error('Warning: could not load the saved OAuth2 credentials: $e\n' |
136 'Obtaining new credentials...'); | 131 'Obtaining new credentials...'); |
137 return null; // null means re-authorize | 132 return null; // null means re-authorize. |
138 }); | 133 } |
139 } | 134 } |
140 | 135 |
141 /// Save the user's OAuth2 credentials to the in-memory cache and the | 136 /// Save the user's OAuth2 credentials to the in-memory cache and the |
142 /// filesystem. | 137 /// filesystem. |
143 Future _saveCredentials(SystemCache cache, Credentials credentials) { | 138 void _saveCredentials(SystemCache cache, Credentials credentials) { |
144 log.fine('Saving OAuth2 credentials.'); | 139 log.fine('Saving OAuth2 credentials.'); |
145 _credentials = credentials; | 140 _credentials = credentials; |
146 var path = _credentialsFile(cache); | 141 var path = _credentialsFile(cache); |
147 return ensureDir(dirname(path)).then((_) => | 142 ensureDir(dirname(path)); |
148 writeTextFile(path, credentials.toJson(), dontLogContents: true)); | 143 writeTextFile(path, credentials.toJson(), dontLogContents: true); |
149 } | 144 } |
150 | 145 |
151 /// The path to the file in which the user's OAuth2 credentials are stored. | 146 /// The path to the file in which the user's OAuth2 credentials are stored. |
152 String _credentialsFile(SystemCache cache) => | 147 String _credentialsFile(SystemCache cache) => |
153 join(cache.rootDir, 'credentials.json'); | 148 join(cache.rootDir, 'credentials.json'); |
154 | 149 |
155 /// Gets the user to authorize pub as a client of pub.dartlang.org via oauth2. | 150 /// Gets the user to authorize pub as a client of pub.dartlang.org via oauth2. |
156 /// Returns a Future that will complete to a fully-authorized [Client]. | 151 /// Returns a Future that will complete to a fully-authorized [Client]. |
157 Future<Client> _authorize() { | 152 Future<Client> _authorize() { |
158 // Allow the tests to inject their own token endpoint URL. | 153 // Allow the tests to inject their own token endpoint URL. |
(...skipping 11 matching lines...) Expand all Loading... |
170 tokenEndpoint, | 165 tokenEndpoint, |
171 httpClient: httpClient); | 166 httpClient: httpClient); |
172 | 167 |
173 // Spin up a one-shot HTTP server to receive the authorization code from the | 168 // Spin up a one-shot HTTP server to receive the authorization code from the |
174 // Google OAuth2 server via redirect. This server will close itself as soon as | 169 // Google OAuth2 server via redirect. This server will close itself as soon as |
175 // the code is received. | 170 // the code is received. |
176 var completer = new Completer(); | 171 var completer = new Completer(); |
177 var server = new HttpServer(); | 172 var server = new HttpServer(); |
178 server.addRequestHandler((request) => request.path == "/", | 173 server.addRequestHandler((request) => request.path == "/", |
179 (request, response) { | 174 (request, response) { |
180 chainToCompleter(new Future.immediate(null).then((_) { | 175 chainToCompleter(defer(() { |
181 log.message('Authorization received, processing...'); | 176 log.message('Authorization received, processing...'); |
182 var queryString = request.queryString; | 177 var queryString = request.queryString; |
183 if (queryString == null) queryString = ''; | 178 if (queryString == null) queryString = ''; |
184 response.statusCode = 302; | 179 response.statusCode = 302; |
185 response.headers.set('location', 'http://pub.dartlang.org/authorized'); | 180 response.headers.set('location', 'http://pub.dartlang.org/authorized'); |
186 response.outputStream.close(); | 181 response.outputStream.close(); |
187 return grant.handleAuthorizationResponse(queryToMap(queryString)); | 182 return grant.handleAuthorizationResponse(queryToMap(queryString)); |
188 }).then((client) { | 183 }).then((client) { |
189 server.close(); | 184 server.close(); |
190 return client; | 185 return client; |
191 }), completer); | 186 }), completer); |
192 }); | 187 }); |
193 server.listen('127.0.0.1', 0); | 188 server.listen('127.0.0.1', 0); |
194 | 189 |
195 var authUrl = grant.getAuthorizationUrl( | 190 var authUrl = grant.getAuthorizationUrl( |
196 Uri.parse('http://localhost:${server.port}'), scopes: _scopes); | 191 Uri.parse('http://localhost:${server.port}'), scopes: _scopes); |
197 | 192 |
198 log.message( | 193 log.message( |
199 'Pub needs your authorization to upload packages on your behalf.\n' | 194 'Pub needs your authorization to upload packages on your behalf.\n' |
200 'In a web browser, go to $authUrl\n' | 195 'In a web browser, go to $authUrl\n' |
201 'Then click "Allow access".\n\n' | 196 'Then click "Allow access".\n\n' |
202 'Waiting for your authorization...'); | 197 'Waiting for your authorization...'); |
203 | 198 |
204 return completer.future.then((client) { | 199 return completer.future.then((client) { |
205 log.message('Successfully authorized.\n'); | 200 log.message('Successfully authorized.\n'); |
206 return client; | 201 return client; |
207 }); | 202 }); |
208 } | 203 } |
OLD | NEW |