| 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:io'; | 8 import 'dart:io'; |
| 8 import 'dart:uri'; | 9 import 'dart:uri'; |
| 9 | 10 |
| 10 // TODO(nweiz): Make this a "package:" URL, or something nicer than this. | 11 // TODO(nweiz): Make this a "package:" URL, or something nicer than this. |
| 11 import '../../pkg/oauth2/lib/oauth2.dart'; | 12 import '../../pkg/oauth2/lib/oauth2.dart'; |
| 12 import 'http.dart'; | 13 import 'http.dart'; |
| 13 import 'io.dart'; | 14 import 'io.dart'; |
| 14 import 'log.dart' as log; | 15 import 'log.dart' as log; |
| 15 import 'system_cache.dart'; | 16 import 'system_cache.dart'; |
| 16 import 'utils.dart'; | 17 import 'utils.dart'; |
| (...skipping 29 matching lines...) Expand all Loading... |
| 46 final _scopes = ['https://www.googleapis.com/auth/userinfo.email']; | 47 final _scopes = ['https://www.googleapis.com/auth/userinfo.email']; |
| 47 | 48 |
| 48 /// 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 |
| 49 /// the same as the credentials file stored in the system cache. | 50 /// the same as the credentials file stored in the system cache. |
| 50 Credentials _credentials; | 51 Credentials _credentials; |
| 51 | 52 |
| 52 /// Delete the cached credentials, if they exist. | 53 /// Delete the cached credentials, if they exist. |
| 53 Future clearCredentials(SystemCache cache) { | 54 Future clearCredentials(SystemCache cache) { |
| 54 _credentials = null; | 55 _credentials = null; |
| 55 var credentialsFile = _credentialsFile(cache); | 56 var credentialsFile = _credentialsFile(cache); |
| 56 return fileExists(credentialsFile).chain((exists) { | 57 return fileExists(credentialsFile).then((exists) { |
| 57 if (exists) return deleteFile(credentialsFile); | 58 if (exists) return deleteFile(credentialsFile); |
| 58 return new Future.immediate(null); | 59 return new Future.immediate(null); |
| 59 }); | 60 }); |
| 60 } | 61 } |
| 61 | 62 |
| 62 /// Asynchronously passes an OAuth2 [Client] to [fn], and closes the client when | 63 /// Asynchronously passes an OAuth2 [Client] to [fn], and closes the client when |
| 63 /// the [Future] returned by [fn] completes. | 64 /// the [Future] returned by [fn] completes. |
| 64 /// | 65 /// |
| 65 /// This takes care of loading and saving the client's credentials, as well as | 66 /// 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 | 67 /// prompting the user for their authorization. It will also re-authorize and |
| 67 /// re-run [fn] if a recoverable authorization error is detected. | 68 /// re-run [fn] if a recoverable authorization error is detected. |
| 68 Future withClient(SystemCache cache, Future fn(Client client)) { | 69 Future withClient(SystemCache cache, Future fn(Client client)) { |
| 69 return _getClient(cache).chain((client) { | 70 return _getClient(cache).then((client) { |
| 70 var completer = new Completer(); | 71 var completer = new Completer(); |
| 71 var future = fn(client); | 72 var future = fn(client); |
| 72 future.onComplete((_) { | 73 future.whenComplete(() { |
| 73 try { | 74 try { |
| 74 client.close(); | 75 client.close(); |
| 75 // Be sure to save the credentials even when an error happens. Also be | 76 // Be sure to save the credentials even when an error happens. Also be |
| 76 // sure to pipe the exception from `future` to `completer`. | 77 // sure to pipe the exception from `future` to `completer`. |
| 77 chainToCompleter( | 78 chainToCompleter( |
| 78 _saveCredentials(cache, client.credentials).chain((_) => future), | 79 _saveCredentials(cache, client.credentials).then((_) => future), |
| 79 completer); | 80 completer); |
| 80 } catch (e, stackTrace) { | 81 } catch (e, stackTrace) { |
| 81 // onComplete will drop exceptions on the floor. We want to ensure that | 82 // whenComplete will drop exceptions on the floor. We want to ensure |
| 82 // any programming errors here don't go un-noticed. See issue 4127. | 83 // that any programming errors here don't go un-noticed. See issue 4127. |
| 83 completer.completeException(e, stackTrace); | 84 completer.completeException(e, stackTrace); |
| 84 } | 85 } |
| 85 }); | 86 }); |
| 86 return completer.future; | 87 return completer.future; |
| 87 }).transformException((e) { | 88 }).catchError((e) { |
| 88 if (e is ExpirationException) { | 89 if (e is ExpirationException) { |
| 89 log.error("Pub's authorization to upload packages has expired and " | 90 log.error("Pub's authorization to upload packages has expired and " |
| 90 "can't be automatically refreshed."); | 91 "can't be automatically refreshed."); |
| 91 return withClient(cache, fn); | 92 return withClient(cache, fn); |
| 92 } else if (e is AuthorizationException) { | 93 } else if (e is AuthorizationException) { |
| 93 var message = "OAuth2 authorization failed"; | 94 var message = "OAuth2 authorization failed"; |
| 94 if (e.description != null) message = "$message (${e.description})"; | 95 if (e.description != null) message = "$message (${e.description})"; |
| 95 log.error("$message."); | 96 log.error("$message."); |
| 96 return clearCredentials(cache).chain((_) => withClient(cache, fn)); | 97 return clearCredentials(cache).then((_) => withClient(cache, fn)); |
| 97 } else { | 98 } else { |
| 98 throw e; | 99 throw e; |
| 99 } | 100 } |
| 100 }); | 101 }); |
| 101 } | 102 } |
| 102 | 103 |
| 103 /// Gets a new OAuth2 client. If saved credentials are available, those are | 104 /// Gets a new OAuth2 client. If saved credentials are available, those are |
| 104 /// used; otherwise, the user is prompted to authorize the pub client. | 105 /// used; otherwise, the user is prompted to authorize the pub client. |
| 105 Future<Client> _getClient(SystemCache cache) { | 106 Future<Client> _getClient(SystemCache cache) { |
| 106 return _loadCredentials(cache).chain((credentials) { | 107 return _loadCredentials(cache).then((credentials) { |
| 107 if (credentials == null) return _authorize(); | 108 if (credentials == null) return _authorize(); |
| 108 return new Future.immediate(new Client( | 109 return new Future.immediate(new Client( |
| 109 _identifier, _secret, credentials, httpClient: curlClient)); | 110 _identifier, _secret, credentials, httpClient: curlClient)); |
| 110 }).chain((client) { | 111 }).then((client) { |
| 111 return _saveCredentials(cache, client.credentials).then((_) => client); | 112 return _saveCredentials(cache, client.credentials).then((_) => client); |
| 112 }); | 113 }); |
| 113 } | 114 } |
| 114 | 115 |
| 115 /// Loads the user's OAuth2 credentials from the in-memory cache or the | 116 /// Loads the user's OAuth2 credentials from the in-memory cache or the |
| 116 /// filesystem if possible. If the credentials can't be loaded for any reason, | 117 /// filesystem if possible. If the credentials can't be loaded for any reason, |
| 117 /// the returned [Future] will complete to null. | 118 /// the returned [Future] will complete to null. |
| 118 Future<Credentials> _loadCredentials(SystemCache cache) { | 119 Future<Credentials> _loadCredentials(SystemCache cache) { |
| 119 log.fine('Loading OAuth2 credentials.'); | 120 log.fine('Loading OAuth2 credentials.'); |
| 120 | 121 |
| 121 if (_credentials != null) { | 122 if (_credentials != null) { |
| 122 log.fine('Using already-loaded credentials.'); | 123 log.fine('Using already-loaded credentials.'); |
| 123 return new Future.immediate(_credentials); | 124 return new Future.immediate(_credentials); |
| 124 } | 125 } |
| 125 | 126 |
| 126 var path = _credentialsFile(cache); | 127 var path = _credentialsFile(cache); |
| 127 return fileExists(path).chain((credentialsExist) { | 128 return fileExists(path).then((credentialsExist) { |
| 128 if (!credentialsExist) { | 129 if (!credentialsExist) { |
| 129 log.fine('No credentials found at $path.'); | 130 log.fine('No credentials found at $path.'); |
| 130 return new Future.immediate(null); | 131 return new Future.immediate(null); |
| 131 } | 132 } |
| 132 | 133 |
| 133 return readTextFile(_credentialsFile(cache)).then((credentialsJson) { | 134 return readTextFile(_credentialsFile(cache)).then((credentialsJson) { |
| 134 var credentials = new Credentials.fromJson(credentialsJson); | 135 var credentials = new Credentials.fromJson(credentialsJson); |
| 135 if (credentials.isExpired && !credentials.canRefresh) { | 136 if (credentials.isExpired && !credentials.canRefresh) { |
| 136 log.error("Pub's authorization to upload packages has expired and " | 137 log.error("Pub's authorization to upload packages has expired and " |
| 137 "can't be automatically refreshed."); | 138 "can't be automatically refreshed."); |
| 138 return null; // null means re-authorize | 139 return null; // null means re-authorize |
| 139 } | 140 } |
| 140 | 141 |
| 141 return credentials; | 142 return credentials; |
| 142 }); | 143 }); |
| 143 }).transformException((e) { | 144 }).catchError((e) { |
| 144 log.error('Warning: could not load the saved OAuth2 credentials: $e\n' | 145 log.error('Warning: could not load the saved OAuth2 credentials: $e\n' |
| 145 'Obtaining new credentials...'); | 146 'Obtaining new credentials...'); |
| 146 return null; // null means re-authorize | 147 return null; // null means re-authorize |
| 147 }); | 148 }); |
| 148 } | 149 } |
| 149 | 150 |
| 150 /// Save the user's OAuth2 credentials to the in-memory cache and the | 151 /// Save the user's OAuth2 credentials to the in-memory cache and the |
| 151 /// filesystem. | 152 /// filesystem. |
| 152 Future _saveCredentials(SystemCache cache, Credentials credentials) { | 153 Future _saveCredentials(SystemCache cache, Credentials credentials) { |
| 153 log.fine('Saving OAuth2 credentials.'); | 154 log.fine('Saving OAuth2 credentials.'); |
| 154 _credentials = credentials; | 155 _credentials = credentials; |
| 155 var path = _credentialsFile(cache); | 156 var path = _credentialsFile(cache); |
| 156 return ensureDir(dirname(path)).chain((_) => | 157 return ensureDir(dirname(path)).then((_) => |
| 157 writeTextFile(path, credentials.toJson(), dontLogContents: true)); | 158 writeTextFile(path, credentials.toJson(), dontLogContents: true)); |
| 158 } | 159 } |
| 159 | 160 |
| 160 /// The path to the file in which the user's OAuth2 credentials are stored. | 161 /// The path to the file in which the user's OAuth2 credentials are stored. |
| 161 String _credentialsFile(SystemCache cache) => | 162 String _credentialsFile(SystemCache cache) => |
| 162 join(cache.rootDir, 'credentials.json'); | 163 join(cache.rootDir, 'credentials.json'); |
| 163 | 164 |
| 164 /// Gets the user to authorize pub as a client of pub.dartlang.org via oauth2. | 165 /// Gets the user to authorize pub as a client of pub.dartlang.org via oauth2. |
| 165 /// Returns a Future that will complete to a fully-authorized [Client]. | 166 /// Returns a Future that will complete to a fully-authorized [Client]. |
| 166 Future<Client> _authorize() { | 167 Future<Client> _authorize() { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 179 tokenEndpoint, | 180 tokenEndpoint, |
| 180 httpClient: curlClient); | 181 httpClient: curlClient); |
| 181 | 182 |
| 182 // Spin up a one-shot HTTP server to receive the authorization code from the | 183 // Spin up a one-shot HTTP server to receive the authorization code from the |
| 183 // Google OAuth2 server via redirect. This server will close itself as soon as | 184 // Google OAuth2 server via redirect. This server will close itself as soon as |
| 184 // the code is received. | 185 // the code is received. |
| 185 var completer = new Completer(); | 186 var completer = new Completer(); |
| 186 var server = new HttpServer(); | 187 var server = new HttpServer(); |
| 187 server.addRequestHandler((request) => request.path == "/", | 188 server.addRequestHandler((request) => request.path == "/", |
| 188 (request, response) { | 189 (request, response) { |
| 189 chainToCompleter(new Future.immediate(null).chain((_) { | 190 chainToCompleter(new Future.immediate(null).then((_) { |
| 190 log.message('Authorization received, processing...'); | 191 log.message('Authorization received, processing...'); |
| 191 var queryString = request.queryString; | 192 var queryString = request.queryString; |
| 192 if (queryString == null) queryString = ''; | 193 if (queryString == null) queryString = ''; |
| 193 response.statusCode = 302; | 194 response.statusCode = 302; |
| 194 response.headers.set('location', 'http://pub.dartlang.org/authorized'); | 195 response.headers.set('location', 'http://pub.dartlang.org/authorized'); |
| 195 response.outputStream.close(); | 196 response.outputStream.close(); |
| 196 return grant.handleAuthorizationResponse(queryToMap(queryString)); | 197 return grant.handleAuthorizationResponse(queryToMap(queryString)); |
| 197 }).then((client) { | 198 }).then((client) { |
| 198 server.close(); | 199 server.close(); |
| 199 return client; | 200 return client; |
| 200 }), completer); | 201 }), completer); |
| 201 }); | 202 }); |
| 202 server.listen('127.0.0.1', 0); | 203 server.listen('127.0.0.1', 0); |
| 203 | 204 |
| 204 var authUrl = grant.getAuthorizationUrl( | 205 var authUrl = grant.getAuthorizationUrl( |
| 205 new Uri.fromString('http://localhost:${server.port}'), scopes: _scopes); | 206 new Uri.fromString('http://localhost:${server.port}'), scopes: _scopes); |
| 206 | 207 |
| 207 log.message( | 208 log.message( |
| 208 'Pub needs your authorization to upload packages on your behalf.\n' | 209 'Pub needs your authorization to upload packages on your behalf.\n' |
| 209 'In a web browser, go to $authUrl\n' | 210 'In a web browser, go to $authUrl\n' |
| 210 'Then click "Allow access".\n\n' | 211 'Then click "Allow access".\n\n' |
| 211 'Waiting for your authorization...'); | 212 'Waiting for your authorization...'); |
| 212 | 213 |
| 213 return completer.future.then((client) { | 214 return completer.future.then((client) { |
| 214 log.message('Successfully authorized.\n'); | 215 log.message('Successfully authorized.\n'); |
| 215 return client; | 216 return client; |
| 216 }); | 217 }); |
| 217 } | 218 } |
| OLD | NEW |