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