Created
August 12, 2013 01:46
-
-
Save MaxHorstmann/6207756 to your computer and use it in GitHub Desktop.
Suggested fix for Dart issue 12366: Add workaround for Facebook oauth bug to oauth2 package
https://code.google.com/p/dart/issues/detail?id=12366
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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' as JSON; | |
import 'package:http/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. | |
const _EXPIRATION_GRACE = 10; | |
/// 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, | |
DateTime startTime, | |
List<String> scopes) { | |
if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); | |
void validate(bool condition, String message) => | |
_validate(response, tokenEndpoint, condition, message); | |
var contentType = response.headers['content-type']; | |
if (contentType != null) { | |
contentType = ContentType.parse(contentType); | |
} | |
validate(contentType != null && | |
((contentType.value == "application/json")||(contentType.value == "text/plain")), | |
'content-type was "$contentType", expected "application/json" or "text/plain"'); | |
var parameters; | |
try { | |
if (contentType.value == "application/json") | |
{ | |
parameters = JSON.parse(response.body); | |
} | |
else | |
{ | |
parameters = new Map<String,String>(); | |
for (var q in response.body.split("&")) | |
{ | |
var kvp = q.split("="); | |
parameters[kvp[0]] = kvp[1]; | |
} | |
if (!parameters.containsKey("token_type")) | |
{ | |
parameters["token_type"] = "bearer"; | |
} | |
} | |
} catch (e) { | |
// TODO(nweiz): narrow this catch clause once issue 6775 is fixed. | |
validate(false, response.body); | |
} | |
for (var requiredParameter in ['access_token', 'token_type']) { | |
validate(parameters.containsKey(requiredParameter), | |
'did not contain required parameter "$requiredParameter"'); | |
validate(parameters[requiredParameter] is String, | |
'required parameter "$requiredParameter" was not a string, was ' | |
'"${parameters[requiredParameter]}"'); | |
} | |
// TODO(nweiz): support the "mac" token type | |
// (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) | |
validate(parameters['token_type'].toLowerCase() == 'bearer', | |
'"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); | |
var expiresIn = parameters['expires_in']; | |
validate(expiresIn == null || expiresIn is int, | |
'parameter "expires_in" was not an int, was "$expiresIn"'); | |
for (var name in ['refresh_token', 'scope']) { | |
var value = parameters[name]; | |
validate(value == null || value is String, | |
'parameter "$name" was not a string, was "$value"'); | |
} | |
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) { | |
void validate(bool condition, String message) => | |
_validate(response, tokenEndpoint, condition, message); | |
// 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 = ContentType.parse(contentType); | |
} | |
validate(contentType != null && contentType.value == "application/json", | |
'content-type was "$contentType", expected "application/json"'); | |
var parameters; | |
try { | |
parameters = JSON.parse(response.body); | |
} catch (e) { | |
// TODO(nweiz): narrow this catch clause once issue 6775 is fixed. | |
validate(false, 'invalid JSON'); | |
} | |
validate(parameters.containsKey('error'), | |
'did not contain required parameter "error"'); | |
validate(parameters["error"] is String, | |
'required parameter "error" was not a string, was ' | |
'"${parameters["error"]}"'); | |
for (var name in ['error_description', 'error_uri']) { | |
var value = parameters[name]; | |
validate(value == null || value is String, | |
'parameter "$name" was not a string, was "$value"'); | |
} | |
var description = parameters['error_description']; | |
var uriString = parameters['error_uri']; | |
var uri = uriString == null ? null : Uri.parse(uriString); | |
throw new AuthorizationException(parameters['error'], description, uri); | |
} | |
void _validate( | |
http.Response response, | |
Uri tokenEndpoint, | |
bool condition, | |
String message) { | |
if (condition) return; | |
throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' | |
'$message.\n\n${response.body}'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Changes in lines 38-60