Chromium Code Reviews| 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:io'; | |
| 8 import 'dart:uri'; | |
| 9 | |
| 10 import '../../pkg/oauth2/lib/oauth2.dart'; | |
|
Bob Nystrom
2012/11/26 23:39:52
Add a TODO here to do... something... better than
nweiz
2012/11/27 20:15:54
Done.
| |
| 11 import 'curl_client.dart'; | |
| 12 import 'io.dart'; | |
| 13 import 'system_cache.dart'; | |
| 14 import 'utils.dart'; | |
| 15 | |
| 16 export '../../pkg/oauth2/lib/oauth2.dart'; | |
| 17 | |
| 18 /// The pub client's OAuth2 identifier. | |
| 19 final String _identifier = '818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.' | |
|
Bob Nystrom
2012/11/26 23:39:52
You don't need to type annotate final top-level va
nweiz
2012/11/27 20:15:54
Done.
| |
| 20 'googleusercontent.com'; | |
| 21 | |
| 22 /// The pub client's OAuth2 secret. This isn't actually meant to be kept a | |
| 23 /// secret. | |
| 24 final String _secret = 'SWeqj8seoJW0w7_CpEPFLX0K'; | |
| 25 | |
| 26 /// The URL to which the user will be directed to authorize the pub client to | |
| 27 /// get an OAuth2 access token. | |
| 28 /// | |
| 29 /// `access_type=offline` and `approval_prompt=force` ensures that we always get | |
| 30 /// a refresh token from the server. See the [Google OAuth2 documentation][]. | |
| 31 /// | |
| 32 /// [Google OAuth2 documentation]: https://developers.google.com/accounts/docs/O Auth2WebServer#offline | |
| 33 final Uri _authorizationEndpoint = new Uri.fromString( | |
| 34 'https://accounts.google.com/o/oauth2/auth?access_type=offline' | |
| 35 '&approval_prompt=force'); | |
| 36 | |
| 37 /// The URL from which the pub client will request an access token once it's | |
| 38 /// been authorized by the user. | |
| 39 final Uri _tokenEndpoint = new Uri.fromString( | |
| 40 'https://accounts.google.com/o/oauth2/token'); | |
| 41 | |
| 42 /// The OAuth2 scopes that the pub client needs. Currently the client only needs | |
| 43 /// the user's email so that the server can verify their identity. | |
| 44 final List<String> _scopes = ['https://www.googleapis.com/auth/userinfo.email']; | |
| 45 | |
| 46 /// An in-memory cache of the user's OAuth2 credentials. This should always be | |
| 47 /// the same as the credentials file stored in the system cache. | |
| 48 Credentials _credentials; | |
| 49 | |
| 50 /// Asynchronously passes an OAuth2 [Client] to [fn], and closes the client when | |
| 51 /// the [Future] returned by [fn] completes. | |
| 52 /// | |
| 53 /// This takes care of loading and saving the client's credentials, as well as | |
| 54 /// prompting the user for their authorization. | |
| 55 Future withClient(SystemCache cache, Future fn(Client client)) { | |
| 56 return _getClient(cache).chain((client) { | |
| 57 var completer = new Completer(); | |
| 58 var future = fn(client); | |
| 59 future.onComplete((_) { | |
| 60 try { | |
| 61 client.close(); | |
| 62 // Be sure to save the credentials even when an error happens. Also be | |
| 63 // sure to pipe the exception from `future` to `completer`. | |
| 64 chainToCompleter( | |
| 65 _saveCredentials(cache, client.credentials).chain((_) => future), | |
| 66 completer); | |
| 67 } catch (e, stackTrace) { | |
| 68 // onComplete will drop exceptions on the floor. We want to ensure that | |
| 69 // any programming errors here don't go un-noticed. See issue 4127. | |
| 70 completer.completeException(e, stackTrace); | |
| 71 } | |
| 72 }); | |
| 73 return completer.future; | |
| 74 }); | |
| 75 } | |
| 76 | |
| 77 /// Gets a new OAuth2 client. If saved credentials are available, those are | |
| 78 /// used; otherwise, the user is prompted to authorize the pub client. | |
| 79 Future<Client> _getClient(SystemCache cache) { | |
| 80 var httpClient = new CurlClient(); | |
| 81 | |
| 82 return _loadCredentials(cache).chain((credentials) { | |
| 83 if (credentials != null) { | |
| 84 return new Future.immediate(new Client( | |
| 85 _identifier, _secret, credentials, httpClient: httpClient)); | |
| 86 } | |
| 87 | |
| 88 // Allow the tests to inject their own token endpoint URL. | |
| 89 var tokenEndpoint = Platform.environment['_PUB_TEST_TOKEN_ENDPOINT']; | |
| 90 if (tokenEndpoint != null) { | |
| 91 tokenEndpoint = new Uri.fromString(tokenEndpoint); | |
| 92 } else { | |
| 93 tokenEndpoint = _tokenEndpoint; | |
| 94 } | |
| 95 | |
| 96 var grant = new AuthorizationCodeGrant( | |
| 97 _identifier, | |
| 98 _secret, | |
| 99 _authorizationEndpoint, | |
| 100 tokenEndpoint, | |
| 101 httpClient: httpClient); | |
| 102 | |
| 103 // TODO(nweiz): spin up a server on localhost and redirect the user there so | |
| 104 // they don't have to copy/paste the authorization code. | |
| 105 var authUrl = grant.getAuthorizationUrl( | |
| 106 new Uri.fromString('urn:ietf:wg:oauth:2.0:oob'), scopes: _scopes); | |
| 107 | |
| 108 stdout.writeString( | |
| 109 'Pub needs your authorization to upload packages on your behalf.\n' | |
| 110 'Go to $authUrl\n' | |
| 111 'Then click "Allow access" and paste the code below:\n' | |
| 112 '> '); | |
| 113 return readLine().chain(grant.handleAuthorizationCode); | |
| 114 }).chain((client) { | |
| 115 return _saveCredentials(cache, client.credentials).transform((_) => client); | |
| 116 }); | |
| 117 } | |
| 118 | |
| 119 /// Loads the user's OAuth2 credentials from the in-memory cache or the | |
| 120 /// filesystem if possible. If the credentials can't be loaded for any reason, | |
| 121 /// the returned [Future] will complete to null. | |
| 122 Future<Credentials> _loadCredentials(SystemCache cache) { | |
| 123 if (_credentials != null) return new Future.immediate(_credentials); | |
| 124 return fileExists(_credentialsFile(cache)).chain((credentialsExist) { | |
| 125 if (!credentialsExist) return new Future.immediate(null); | |
| 126 | |
| 127 return readTextFile(_credentialsFile(cache)).transform((credentialsJson) { | |
| 128 var credentials = new Credentials.fromJson(credentialsJson); | |
| 129 if (credentials.isExpired && !credentials.canRefresh) { | |
| 130 printError("Pub's authorization to upload packages has expired and " | |
| 131 "can't be automatically refreshed."); | |
|
Bob Nystrom
2012/11/26 23:39:52
We should tell the user what they need to do here.
nweiz
2012/11/27 20:15:54
Returning null here will trigger the re-authorizat
| |
| 132 return null; | |
| 133 } | |
| 134 | |
| 135 return credentials; | |
| 136 }); | |
| 137 }).transformException((e) { | |
| 138 printError('Warning: could not load the saved OAuth2 credentials:' | |
| 139 ' $e\n' | |
| 140 'Obtaining new credentials...'); | |
|
Bob Nystrom
2012/11/26 23:39:52
I don't understand this bit. Where is it obtaining
nweiz
2012/11/27 20:15:54
The "null" return value is a signal that the crede
Bob Nystrom
2012/11/27 21:00:53
Can you add a comment with that?
nweiz
2012/11/27 22:07:34
Done.
| |
| 141 return null; | |
| 142 }); | |
| 143 } | |
| 144 | |
| 145 /// Save the user's OAuth2 credentials to the in-memory cache and the | |
| 146 /// filesystem. | |
| 147 Future _saveCredentials(SystemCache cache, Credentials credentials) { | |
| 148 _credentials = credentials; | |
| 149 var credentialsFile = _credentialsFile(cache); | |
| 150 return ensureDir(dirname(credentialsFile)).chain((_) => | |
| 151 writeTextFile(credentialsFile, credentials.toJson())); | |
| 152 } | |
| 153 | |
| 154 /// The path to the file in which the user's OAuth2 credentials are stored. | |
| 155 String _credentialsFile(SystemCache cache) => | |
| 156 join(cache.rootDir, 'credentials.json'); | |
| OLD | NEW |