Chromium Code Reviews| Index: pkg/oauth2/lib/src/handle_access_token_response.dart |
| diff --git a/pkg/oauth2/lib/src/handle_access_token_response.dart b/pkg/oauth2/lib/src/handle_access_token_response.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..97b817d1caf6bd46c0eec6a6f2337e62cba92c5d |
| --- /dev/null |
| +++ b/pkg/oauth2/lib/src/handle_access_token_response.dart |
| @@ -0,0 +1,152 @@ |
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +library handle_access_token_response; |
| + |
| +import 'dart:io'; |
| +import 'dart:json'; |
| +import 'dart:uri'; |
| + |
| +import '../../../http/lib/http.dart' as http; |
| + |
| +import 'credentials.dart'; |
| +import 'authorization_exception.dart'; |
| + |
| +/// The amount of time, in seconds, to add as a "grace period" for credential |
| +/// expiration. This allows credential expiration checks to remain valid for a |
| +/// reasonable amount of time. |
| +final int _EXPIRATION_GRACE = 10; |
|
Bob Nystrom
2012/11/16 19:53:30
"final int" -> "const".
nweiz
2012/11/17 01:06:27
Done.
|
| + |
| +/// Handles a response from the authorization server that contains an access |
| +/// token. This response format is common across several different components of |
| +/// the OAuth2 flow. |
| +Credentials handleAccessTokenResponse( |
| + http.Response response, |
| + Uri tokenEndpoint, |
| + Date startTime, |
| + List<String> scopes) { |
| + if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); |
| + |
| + var contentType = response.headers['content-type']; |
| + if (contentType != null) { |
| + contentType = new ContentType.fromString(contentType); |
| + } |
| + if (contentType == null || contentType.value != "application/json") { |
| + throw new FormatException('Invalid OAuth response for ' |
| + '"$tokenEndpoint": content-type was "$contentType", expected ' |
| + '"application/json".'); |
| + } |
| + |
| + var parameters; |
| + try { |
| + parameters = JSON.parse(response.body); |
| + } catch (e) { |
|
Bob Nystrom
2012/11/16 19:53:30
Restrict this:
} on FormatException catch (e) {
nweiz
2012/11/17 01:06:27
JSON.parse doesn't throw a FormatException :-/.
Bob Nystrom
2012/11/19 21:37:10
Restrict it to a JSONParseException? We definitely
nweiz
2012/11/19 22:20:11
It doesn't throw a JSONParseException either :-/.
|
| + throw new FormatException('Invalid OAuth response for ' |
| + '"$tokenEndpoint": invalid JSON.\n\n${response.body}'); |
|
Bob Nystrom
2012/11/16 19:53:30
How about a helper function for all of these throw
nweiz
2012/11/17 01:06:27
Done.
|
| + } |
| + |
| + for (var requiredParameter in ['access_token', 'token_type']) { |
| + if (!parameters.containsKey(requiredParameter)) { |
| + throw new FormatException('Invalid OAuth response for ' |
| + '"$tokenEndpoint": did not contain required parameter ' |
| + '"$requiredParameter".\n\n${response.body}'); |
| + } else if (parameters[requiredParameter] is! String) { |
| + throw new FormatException('Invalid OAuth response for ' |
| + '"$tokenEndpoint": required parameter "$requiredParameter" was ' |
| + 'not a string, was "${parameters[requiredParameter]}".\n\n' |
| + '${response.body}'); |
| + } |
| + } |
| + |
| + // TODO(nweiz): support the "mac" token type |
| + // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) |
| + if (parameters['token_type'].toLowerCase() != 'bearer') { |
| + throw new FormatException('Invalid OAuth response for ' |
| + '"$tokenEndpoint": unknown token type ' |
| + '"${parameters['token_type']}".\n\n${response.body}'); |
| + } |
| + |
| + var expiresIn = parameters['expires_in']; |
| + if (expiresIn != null && expiresIn is! int) { |
| + throw new FormatException('Invalid OAuth response for ' |
| + '"$tokenEndpoint": parameter "expires_in" was not an int, was ' |
| + '"$expiresIn".\n\n${response.body}'); |
| + } |
| + |
| + for (var name in ['refresh_token', 'scope']) { |
| + var value = parameters[name]; |
| + if (value == null || value is String) continue; |
| + throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' |
| + 'parameter "$name" was not a string, was "$value".\n\n' |
| + '${response.body}'); |
| + } |
| + |
| + var scope = parameters['scope']; |
| + if (scope != null) scopes = scope.split(" "); |
| + |
| + var expiration = expiresIn == null ? null : |
| + startTime.add(new Duration(seconds: expiresIn - _EXPIRATION_GRACE)); |
| + |
| + return new Credentials( |
| + parameters['access_token'], |
| + parameters['refresh_token'], |
| + tokenEndpoint, |
| + scopes, |
| + expiration); |
| +} |
| + |
| +/// Throws the appropriate exception for an error response from the |
| +/// authorization server. |
| +void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { |
| + // OAuth2 mandates a 400 or 401 response code for access token error |
| + // responses. If it's not a 400 reponse, the server is either broken or |
| + // off-spec. |
| + if (response.statusCode != 400 && response.statusCode != 401) { |
| + var reason = ''; |
| + if (response.reasonPhrase != null && !response.reasonPhrase.isEmpty) { |
| + ' ${response.reasonPhrase}'; |
| + } |
| + throw new FormatException('OAuth request for "$tokenEndpoint" failed ' |
| + 'with status ${response.statusCode}$reason.\n\n${response.body}'); |
| + } |
| + |
| + var contentType = response.headers['content-type']; |
| + if (contentType != null) { |
| + contentType = new ContentType.fromString(contentType); |
| + } |
| + if (contentType == null || contentType.value != "application/json") { |
| + throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' |
| + 'content-type was "$contentType", expected "application/json".'); |
| + } |
| + |
| + var parameters; |
| + try { |
| + parameters = JSON.parse(response.body); |
| + } catch (e) { |
| + throw new FormatException('Invalid OAuth response for ' |
| + '"$tokenEndpoint": invalid JSON.\n\n${response.body}'); |
| + } |
| + |
| + if (!parameters.containsKey('error')) { |
| + throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' |
| + 'did not contain required parameter "error".\n\n${response.body}'); |
| + } else if (parameters["error"] is! String) { |
| + throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' |
| + 'required parameter "error" was not a string, was ' |
| + '"${parameters["error"]}"\n\n${response.body}.'); |
| + } |
| + |
| + for (var name in ['error_description', 'error_uri']) { |
| + var value = parameters[name]; |
| + if (value == null || value is String) continue; |
| + throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' |
| + 'parameter "$name" was not a string, was "$value".\n\n' |
| + '${response.body}'); |
| + } |
| + |
| + var description = parameters['error_description']; |
| + var uriString = parameters['error_uri']; |
| + var uri = uriString == null ? null : new Uri.fromString(uriString); |
| + throw new AuthorizationException(parameters['error'], description, uri); |
| +} |