| 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; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:io'; | |
| 9 import 'dart:uri'; | |
| 10 | |
| 11 import 'package:oauth2/oauth2.dart'; | |
| 12 import 'package:pathos/path.dart' as path; | |
| 13 | |
| 14 import 'http.dart'; | |
| 15 import 'io.dart'; | |
| 16 import 'log.dart' as log; | |
| 17 import 'safe_http_server.dart'; | |
| 18 import 'system_cache.dart'; | |
| 19 import 'utils.dart'; | |
| 20 | |
| 21 export 'package:oauth2/oauth2.dart'; | |
| 22 | |
| 23 /// The pub client's OAuth2 identifier. | |
| 24 final _identifier = '818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.' | |
| 25 'googleusercontent.com'; | |
| 26 | |
| 27 /// The pub client's OAuth2 secret. This isn't actually meant to be kept a | |
| 28 /// secret. | |
| 29 final _secret = 'SWeqj8seoJW0w7_CpEPFLX0K'; | |
| 30 | |
| 31 /// The URL to which the user will be directed to authorize the pub client to | |
| 32 /// get an OAuth2 access token. | |
| 33 /// | |
| 34 /// `access_type=offline` and `approval_prompt=force` ensures that we always get | |
| 35 /// a refresh token from the server. See the [Google OAuth2 documentation][]. | |
| 36 /// | |
| 37 /// [Google OAuth2 documentation]: https://developers.google.com/accounts/docs/O
Auth2WebServer#offline | |
| 38 final authorizationEndpoint = Uri.parse( | |
| 39 'https://accounts.google.com/o/oauth2/auth?access_type=offline' | |
| 40 '&approval_prompt=force'); | |
| 41 | |
| 42 /// The URL from which the pub client will request an access token once it's | |
| 43 /// been authorized by the user. This can be controlled externally by setting | |
| 44 /// the _PUB_TEST_TOKEN_ENDPOINT environment variable. | |
| 45 Uri get tokenEndpoint { | |
| 46 var tokenEndpoint = Platform.environment['_PUB_TEST_TOKEN_ENDPOINT']; | |
| 47 if (tokenEndpoint != null) { | |
| 48 return Uri.parse(tokenEndpoint); | |
| 49 } else { | |
| 50 return _tokenEndpoint; | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 final _tokenEndpoint = Uri.parse('https://accounts.google.com/o/oauth2/token'); | |
| 55 | |
| 56 /// The OAuth2 scopes that the pub client needs. Currently the client only needs | |
| 57 /// the user's email so that the server can verify their identity. | |
| 58 final _scopes = ['https://www.googleapis.com/auth/userinfo.email']; | |
| 59 | |
| 60 /// An in-memory cache of the user's OAuth2 credentials. This should always be | |
| 61 /// the same as the credentials file stored in the system cache. | |
| 62 Credentials _credentials; | |
| 63 | |
| 64 /// Delete the cached credentials, if they exist. | |
| 65 void clearCredentials(SystemCache cache) { | |
| 66 _credentials = null; | |
| 67 var credentialsFile = _credentialsFile(cache); | |
| 68 if (entryExists(credentialsFile)) deleteEntry(credentialsFile); | |
| 69 } | |
| 70 | |
| 71 /// Asynchronously passes an OAuth2 [Client] to [fn], and closes the client when | |
| 72 /// the [Future] returned by [fn] completes. | |
| 73 /// | |
| 74 /// This takes care of loading and saving the client's credentials, as well as | |
| 75 /// prompting the user for their authorization. It will also re-authorize and | |
| 76 /// re-run [fn] if a recoverable authorization error is detected. | |
| 77 Future withClient(SystemCache cache, Future fn(Client client)) { | |
| 78 return _getClient(cache).then((client) { | |
| 79 var completer = new Completer(); | |
| 80 return fn(client).whenComplete(() { | |
| 81 client.close(); | |
| 82 // Be sure to save the credentials even when an error happens. | |
| 83 _saveCredentials(cache, client.credentials); | |
| 84 }); | |
| 85 }).catchError((error) { | |
| 86 if (error is ExpirationException) { | |
| 87 log.error("Pub's authorization to upload packages has expired and " | |
| 88 "can't be automatically refreshed."); | |
| 89 return withClient(cache, fn); | |
| 90 } else if (error is AuthorizationException) { | |
| 91 var message = "OAuth2 authorization failed"; | |
| 92 if (error.description != null) { | |
| 93 message = "$message (${error.description})"; | |
| 94 } | |
| 95 log.error("$message."); | |
| 96 clearCredentials(cache); | |
| 97 return withClient(cache, fn); | |
| 98 } else { | |
| 99 throw error; | |
| 100 } | |
| 101 }); | |
| 102 } | |
| 103 | |
| 104 /// Gets a new OAuth2 client. If saved credentials are available, those are | |
| 105 /// used; otherwise, the user is prompted to authorize the pub client. | |
| 106 Future<Client> _getClient(SystemCache cache) { | |
| 107 return new Future.sync(() { | |
| 108 var credentials = _loadCredentials(cache); | |
| 109 if (credentials == null) return _authorize(); | |
| 110 | |
| 111 var client = new Client(_identifier, _secret, credentials, | |
| 112 httpClient: httpClient); | |
| 113 _saveCredentials(cache, client.credentials); | |
| 114 return client; | |
| 115 }); | |
| 116 } | |
| 117 | |
| 118 /// Loads the user's OAuth2 credentials from the in-memory cache or the | |
| 119 /// filesystem if possible. If the credentials can't be loaded for any reason, | |
| 120 /// the returned [Future] will complete to null. | |
| 121 Credentials _loadCredentials(SystemCache cache) { | |
| 122 log.fine('Loading OAuth2 credentials.'); | |
| 123 | |
| 124 try { | |
| 125 if (_credentials != null) return _credentials; | |
| 126 | |
| 127 var path = _credentialsFile(cache); | |
| 128 if (!fileExists(path)) return null; | |
| 129 | |
| 130 var credentials = new Credentials.fromJson(readTextFile(path)); | |
| 131 if (credentials.isExpired && !credentials.canRefresh) { | |
| 132 log.error("Pub's authorization to upload packages has expired and " | |
| 133 "can't be automatically refreshed."); | |
| 134 return null; // null means re-authorize. | |
| 135 } | |
| 136 | |
| 137 return credentials; | |
| 138 } catch (e) { | |
| 139 log.error('Warning: could not load the saved OAuth2 credentials: $e\n' | |
| 140 'Obtaining new credentials...'); | |
| 141 return null; // null means re-authorize. | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 /// Save the user's OAuth2 credentials to the in-memory cache and the | |
| 146 /// filesystem. | |
| 147 void _saveCredentials(SystemCache cache, Credentials credentials) { | |
| 148 log.fine('Saving OAuth2 credentials.'); | |
| 149 _credentials = credentials; | |
| 150 var credentialsPath = _credentialsFile(cache); | |
| 151 ensureDir(path.dirname(credentialsPath)); | |
| 152 writeTextFile(credentialsPath, credentials.toJson(), dontLogContents: true); | |
| 153 } | |
| 154 | |
| 155 /// The path to the file in which the user's OAuth2 credentials are stored. | |
| 156 String _credentialsFile(SystemCache cache) => | |
| 157 path.join(cache.rootDir, 'credentials.json'); | |
| 158 | |
| 159 /// Gets the user to authorize pub as a client of pub.dartlang.org via oauth2. | |
| 160 /// Returns a Future that will complete to a fully-authorized [Client]. | |
| 161 Future<Client> _authorize() { | |
| 162 var grant = new AuthorizationCodeGrant( | |
| 163 _identifier, | |
| 164 _secret, | |
| 165 authorizationEndpoint, | |
| 166 tokenEndpoint, | |
| 167 httpClient: httpClient); | |
| 168 | |
| 169 // Spin up a one-shot HTTP server to receive the authorization code from the | |
| 170 // Google OAuth2 server via redirect. This server will close itself as soon as | |
| 171 // the code is received. | |
| 172 return SafeHttpServer.bind('127.0.0.1', 0).then((server) { | |
| 173 var authUrl = grant.getAuthorizationUrl( | |
| 174 Uri.parse('http://localhost:${server.port}'), scopes: _scopes); | |
| 175 | |
| 176 log.message( | |
| 177 'Pub needs your authorization to upload packages on your behalf.\n' | |
| 178 'In a web browser, go to $authUrl\n' | |
| 179 'Then click "Allow access".\n\n' | |
| 180 'Waiting for your authorization...'); | |
| 181 return server.first.then((request) { | |
| 182 var response = request.response; | |
| 183 if (request.uri.path == "/") { | |
| 184 log.message('Authorization received, processing...'); | |
| 185 var queryString = request.uri.query; | |
| 186 if (queryString == null) queryString = ''; | |
| 187 response.statusCode = 302; | |
| 188 response.headers.set('location', | |
| 189 'http://pub.dartlang.org/authorized'); | |
| 190 response.close(); | |
| 191 return grant.handleAuthorizationResponse(queryToMap(queryString)) | |
| 192 .then((client) { | |
| 193 server.close(); | |
| 194 return client; | |
| 195 }); | |
| 196 } else { | |
| 197 response.statusCode = 404; | |
| 198 response.close(); | |
| 199 } | |
| 200 }); | |
| 201 }) | |
| 202 .then((client) { | |
| 203 log.message('Successfully authorized.\n'); | |
| 204 return client; | |
| 205 }); | |
| 206 } | |
| OLD | NEW |