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 |