Chromium Code Reviews| 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 // Make sure all errors propagate through future. |
| 70 return defer(() { | |
| 71 var client = _getClient(cache); | |
| 70 var completer = new Completer(); | 72 var completer = new Completer(); |
| 71 return fn(client).whenComplete(() { | 73 return fn(client).whenComplete(() { |
| 72 client.close(); | 74 client.close(); |
| 73 // Be sure to save the credentials even when an error happens. | 75 // Be sure to save the credentials even when an error happens. |
| 74 return _saveCredentials(cache, client.credentials); | 76 _saveCredentials(cache, client.credentials); |
| 75 }); | 77 }); |
| 76 }).catchError((asyncError) { | 78 }).catchError((asyncError) { |
| 77 if (asyncError.error is ExpirationException) { | 79 if (asyncError.error is ExpirationException) { |
| 78 log.error("Pub's authorization to upload packages has expired and " | 80 log.error("Pub's authorization to upload packages has expired and " |
| 79 "can't be automatically refreshed."); | 81 "can't be automatically refreshed."); |
| 80 return withClient(cache, fn); | 82 return withClient(cache, fn); |
| 81 } else if (asyncError.error is AuthorizationException) { | 83 } else if (asyncError.error is AuthorizationException) { |
| 82 var message = "OAuth2 authorization failed"; | 84 var message = "OAuth2 authorization failed"; |
| 83 if (asyncError.error.description != null) { | 85 if (asyncError.error.description != null) { |
| 84 message = "$message (${asyncError.error.description})"; | 86 message = "$message (${asyncError.error.description})"; |
| 85 } | 87 } |
| 86 log.error("$message."); | 88 log.error("$message."); |
| 87 return clearCredentials(cache).then((_) => withClient(cache, fn)); | 89 clearCredentials(cache); |
| 90 return withClient(cache, fn); | |
| 88 } else { | 91 } else { |
| 89 throw asyncError; | 92 throw asyncError; |
| 90 } | 93 } |
| 91 }); | 94 }); |
| 92 } | 95 } |
| 93 | 96 |
| 94 /// Gets a new OAuth2 client. If saved credentials are available, those are | 97 /// Gets a new OAuth2 client. If saved credentials are available, those are |
| 95 /// used; otherwise, the user is prompted to authorize the pub client. | 98 /// used; otherwise, the user is prompted to authorize the pub client. |
| 96 Future<Client> _getClient(SystemCache cache) { | 99 Client _getClient(SystemCache cache) { |
| 97 return _loadCredentials(cache).then((credentials) { | 100 var credentials = _loadCredentials(cache); |
| 98 if (credentials == null) return _authorize(); | 101 if (credentials == null) return _authorize(); |
| 99 return new Client(_identifier, _secret, credentials, | 102 var client = new Client(_identifier, _secret, credentials, |
| 100 httpClient: curlClient); | 103 httpClient: curlClient); |
| 101 }).then((client) { | 104 _saveCredentials(cache, client.credentials); |
| 102 return _saveCredentials(cache, client.credentials).then((_) => client); | 105 return client; |
| 103 }); | |
| 104 } | 106 } |
| 105 | 107 |
| 106 /// Loads the user's OAuth2 credentials from the in-memory cache or the | 108 /// 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, | 109 /// filesystem if possible. If the credentials can't be loaded for any reason, |
| 108 /// the returned [Future] will complete to null. | 110 /// the returned [Future] will complete to null. |
| 109 Future<Credentials> _loadCredentials(SystemCache cache) { | 111 Credentials _loadCredentials(SystemCache cache) { |
| 110 log.fine('Loading OAuth2 credentials.'); | 112 log.fine('Loading OAuth2 credentials.'); |
| 111 | 113 |
| 112 if (_credentials != null) { | 114 try { |
| 113 log.fine('Using already-loaded credentials.'); | 115 if (_credentials != null) return _credentials; |
|
nweiz
2013/02/01 02:05:55
Why no more logging?
Bob Nystrom
2013/02/01 23:17:21
Two reasons:
1. The logging is most helpful when
| |
| 114 return new Future.immediate(_credentials); | |
| 115 } | |
| 116 | 116 |
| 117 var path = _credentialsFile(cache); | 117 var path = _credentialsFile(cache); |
| 118 return fileExists(path).then((credentialsExist) { | 118 var credentialsExist = fileExists(path); |
| 119 if (!credentialsExist) { | 119 if (!credentialsExist) return; |
|
nweiz
2013/02/01 02:05:55
Style nit: merge this and the previous line.
Bob Nystrom
2013/02/01 23:17:21
Done.
| |
| 120 log.fine('No credentials found at $path.'); | 120 |
| 121 return; | 121 var credentials = new Credentials.fromJson( |
| 122 readTextFile(_credentialsFile(cache))); | |
|
nweiz
2013/02/01 02:05:55
"_credentialsFile(cache)" -> "path"
Bob Nystrom
2013/02/01 23:17:21
Done.
| |
| 123 if (credentials.isExpired && !credentials.canRefresh) { | |
| 124 log.error("Pub's authorization to upload packages has expired and " | |
| 125 "can't be automatically refreshed."); | |
| 126 return null; // null means re-authorize. | |
| 122 } | 127 } |
| 123 | 128 |
| 124 return readTextFile(_credentialsFile(cache)).then((credentialsJson) { | 129 return credentials; |
| 125 var credentials = new Credentials.fromJson(credentialsJson); | 130 } 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' | 131 log.error('Warning: could not load the saved OAuth2 credentials: $e\n' |
| 136 'Obtaining new credentials...'); | 132 'Obtaining new credentials...'); |
| 137 return null; // null means re-authorize | 133 return null; // null means re-authorize. |
| 138 }); | 134 } |
| 139 } | 135 } |
| 140 | 136 |
| 141 /// Save the user's OAuth2 credentials to the in-memory cache and the | 137 /// Save the user's OAuth2 credentials to the in-memory cache and the |
| 142 /// filesystem. | 138 /// filesystem. |
| 143 Future _saveCredentials(SystemCache cache, Credentials credentials) { | 139 void _saveCredentials(SystemCache cache, Credentials credentials) { |
| 144 log.fine('Saving OAuth2 credentials.'); | 140 log.fine('Saving OAuth2 credentials.'); |
| 145 _credentials = credentials; | 141 _credentials = credentials; |
| 146 var path = _credentialsFile(cache); | 142 var path = _credentialsFile(cache); |
| 147 return ensureDir(dirname(path)).then((_) => | 143 ensureDir(dirname(path)); |
| 148 writeTextFile(path, credentials.toJson(), dontLogContents: true)); | 144 writeTextFile(path, credentials.toJson(), dontLogContents: true); |
| 149 } | 145 } |
| 150 | 146 |
| 151 /// The path to the file in which the user's OAuth2 credentials are stored. | 147 /// The path to the file in which the user's OAuth2 credentials are stored. |
| 152 String _credentialsFile(SystemCache cache) => | 148 String _credentialsFile(SystemCache cache) => |
| 153 join(cache.rootDir, 'credentials.json'); | 149 join(cache.rootDir, 'credentials.json'); |
| 154 | 150 |
| 155 /// Gets the user to authorize pub as a client of pub.dartlang.org via oauth2. | 151 /// 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]. | 152 /// Returns a Future that will complete to a fully-authorized [Client]. |
| 157 Future<Client> _authorize() { | 153 Future<Client> _authorize() { |
| 158 // Allow the tests to inject their own token endpoint URL. | 154 // Allow the tests to inject their own token endpoint URL. |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 170 tokenEndpoint, | 166 tokenEndpoint, |
| 171 httpClient: curlClient); | 167 httpClient: curlClient); |
| 172 | 168 |
| 173 // Spin up a one-shot HTTP server to receive the authorization code from the | 169 // 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 | 170 // Google OAuth2 server via redirect. This server will close itself as soon as |
| 175 // the code is received. | 171 // the code is received. |
| 176 var completer = new Completer(); | 172 var completer = new Completer(); |
| 177 var server = new HttpServer(); | 173 var server = new HttpServer(); |
| 178 server.addRequestHandler((request) => request.path == "/", | 174 server.addRequestHandler((request) => request.path == "/", |
| 179 (request, response) { | 175 (request, response) { |
| 180 chainToCompleter(new Future.immediate(null).then((_) { | 176 chainToCompleter(defer(() { |
| 181 log.message('Authorization received, processing...'); | 177 log.message('Authorization received, processing...'); |
| 182 var queryString = request.queryString; | 178 var queryString = request.queryString; |
| 183 if (queryString == null) queryString = ''; | 179 if (queryString == null) queryString = ''; |
| 184 response.statusCode = 302; | 180 response.statusCode = 302; |
| 185 response.headers.set('location', 'http://pub.dartlang.org/authorized'); | 181 response.headers.set('location', 'http://pub.dartlang.org/authorized'); |
| 186 response.outputStream.close(); | 182 response.outputStream.close(); |
| 187 return grant.handleAuthorizationResponse(queryToMap(queryString)); | 183 return grant.handleAuthorizationResponse(queryToMap(queryString)); |
| 188 }).then((client) { | 184 }).then((client) { |
| 189 server.close(); | 185 server.close(); |
| 190 return client; | 186 return client; |
| 191 }), completer); | 187 }), completer); |
| 192 }); | 188 }); |
| 193 server.listen('127.0.0.1', 0); | 189 server.listen('127.0.0.1', 0); |
| 194 | 190 |
| 195 var authUrl = grant.getAuthorizationUrl( | 191 var authUrl = grant.getAuthorizationUrl( |
| 196 Uri.parse('http://localhost:${server.port}'), scopes: _scopes); | 192 Uri.parse('http://localhost:${server.port}'), scopes: _scopes); |
| 197 | 193 |
| 198 log.message( | 194 log.message( |
| 199 'Pub needs your authorization to upload packages on your behalf.\n' | 195 'Pub needs your authorization to upload packages on your behalf.\n' |
| 200 'In a web browser, go to $authUrl\n' | 196 'In a web browser, go to $authUrl\n' |
| 201 'Then click "Allow access".\n\n' | 197 'Then click "Allow access".\n\n' |
| 202 'Waiting for your authorization...'); | 198 'Waiting for your authorization...'); |
| 203 | 199 |
| 204 return completer.future.then((client) { | 200 return completer.future.then((client) { |
| 205 log.message('Successfully authorized.\n'); | 201 log.message('Successfully authorized.\n'); |
| 206 return client; | 202 return client; |
| 207 }); | 203 }); |
| 208 } | 204 } |
| OLD | NEW |