| 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 handle_access_token_response; | |
| 6 | |
| 7 import 'dart:convert'; | |
| 8 | |
| 9 import 'package:http/http.dart' as http; | |
| 10 import 'package:http_parser/http_parser.dart'; | |
| 11 | |
| 12 import 'credentials.dart'; | |
| 13 import 'authorization_exception.dart'; | |
| 14 | |
| 15 /// The amount of time, in seconds, to add as a "grace period" for credential | |
| 16 /// expiration. This allows credential expiration checks to remain valid for a | |
| 17 /// reasonable amount of time. | |
| 18 const _EXPIRATION_GRACE = 10; | |
| 19 | |
| 20 /// Handles a response from the authorization server that contains an access | |
| 21 /// token. This response format is common across several different components of | |
| 22 /// the OAuth2 flow. | |
| 23 Credentials handleAccessTokenResponse( | |
| 24 http.Response response, | |
| 25 Uri tokenEndpoint, | |
| 26 DateTime startTime, | |
| 27 List<String> scopes) { | |
| 28 if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); | |
| 29 | |
| 30 void validate(bool condition, String message) => | |
| 31 _validate(response, tokenEndpoint, condition, message); | |
| 32 | |
| 33 var contentType = response.headers['content-type']; | |
| 34 if (contentType != null) contentType = new MediaType.parse(contentType); | |
| 35 | |
| 36 // The spec requires a content-type of application/json, but some endpoints | |
| 37 // (e.g. Dropbox) serve it as text/javascript instead. | |
| 38 validate(contentType != null && | |
| 39 (contentType.mimeType == "application/json" || | |
| 40 contentType.mimeType == "text/javascript"), | |
| 41 'content-type was "$contentType", expected "application/json"'); | |
| 42 | |
| 43 var parameters; | |
| 44 try { | |
| 45 parameters = JSON.decode(response.body); | |
| 46 } on FormatException catch (e) { | |
| 47 validate(false, 'invalid JSON'); | |
| 48 } | |
| 49 | |
| 50 for (var requiredParameter in ['access_token', 'token_type']) { | |
| 51 validate(parameters.containsKey(requiredParameter), | |
| 52 'did not contain required parameter "$requiredParameter"'); | |
| 53 validate(parameters[requiredParameter] is String, | |
| 54 'required parameter "$requiredParameter" was not a string, was ' | |
| 55 '"${parameters[requiredParameter]}"'); | |
| 56 } | |
| 57 | |
| 58 // TODO(nweiz): support the "mac" token type | |
| 59 // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) | |
| 60 validate(parameters['token_type'].toLowerCase() == 'bearer', | |
| 61 '"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); | |
| 62 | |
| 63 var expiresIn = parameters['expires_in']; | |
| 64 validate(expiresIn == null || expiresIn is int, | |
| 65 'parameter "expires_in" was not an int, was "$expiresIn"'); | |
| 66 | |
| 67 for (var name in ['refresh_token', 'scope']) { | |
| 68 var value = parameters[name]; | |
| 69 validate(value == null || value is String, | |
| 70 'parameter "$name" was not a string, was "$value"'); | |
| 71 } | |
| 72 | |
| 73 var scope = parameters['scope']; | |
| 74 if (scope != null) scopes = scope.split(" "); | |
| 75 | |
| 76 var expiration = expiresIn == null ? null : | |
| 77 startTime.add(new Duration(seconds: expiresIn - _EXPIRATION_GRACE)); | |
| 78 | |
| 79 return new Credentials( | |
| 80 parameters['access_token'], | |
| 81 parameters['refresh_token'], | |
| 82 tokenEndpoint, | |
| 83 scopes, | |
| 84 expiration); | |
| 85 } | |
| 86 | |
| 87 /// Throws the appropriate exception for an error response from the | |
| 88 /// authorization server. | |
| 89 void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { | |
| 90 void validate(bool condition, String message) => | |
| 91 _validate(response, tokenEndpoint, condition, message); | |
| 92 | |
| 93 // OAuth2 mandates a 400 or 401 response code for access token error | |
| 94 // responses. If it's not a 400 reponse, the server is either broken or | |
| 95 // off-spec. | |
| 96 if (response.statusCode != 400 && response.statusCode != 401) { | |
| 97 var reason = ''; | |
| 98 if (response.reasonPhrase != null && !response.reasonPhrase.isEmpty) { | |
| 99 ' ${response.reasonPhrase}'; | |
| 100 } | |
| 101 throw new FormatException('OAuth request for "$tokenEndpoint" failed ' | |
| 102 'with status ${response.statusCode}$reason.\n\n${response.body}'); | |
| 103 } | |
| 104 | |
| 105 var contentType = response.headers['content-type']; | |
| 106 if (contentType != null) contentType = new MediaType.parse(contentType); | |
| 107 validate(contentType != null && contentType.mimeType == "application/json", | |
| 108 'content-type was "$contentType", expected "application/json"'); | |
| 109 | |
| 110 var parameters; | |
| 111 try { | |
| 112 parameters = JSON.decode(response.body); | |
| 113 } on FormatException catch (e) { | |
| 114 validate(false, 'invalid JSON'); | |
| 115 } | |
| 116 | |
| 117 validate(parameters.containsKey('error'), | |
| 118 'did not contain required parameter "error"'); | |
| 119 validate(parameters["error"] is String, | |
| 120 'required parameter "error" was not a string, was ' | |
| 121 '"${parameters["error"]}"'); | |
| 122 | |
| 123 for (var name in ['error_description', 'error_uri']) { | |
| 124 var value = parameters[name]; | |
| 125 validate(value == null || value is String, | |
| 126 'parameter "$name" was not a string, was "$value"'); | |
| 127 } | |
| 128 | |
| 129 var description = parameters['error_description']; | |
| 130 var uriString = parameters['error_uri']; | |
| 131 var uri = uriString == null ? null : Uri.parse(uriString); | |
| 132 throw new AuthorizationException(parameters['error'], description, uri); | |
| 133 } | |
| 134 | |
| 135 void _validate( | |
| 136 http.Response response, | |
| 137 Uri tokenEndpoint, | |
| 138 bool condition, | |
| 139 String message) { | |
| 140 if (condition) return; | |
| 141 throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' | |
| 142 '$message.\n\n${response.body}'); | |
| 143 } | |
| OLD | NEW |