Bump to 2.2.0

This commit is contained in:
Tobe O 2019-05-02 03:28:38 -04:00
parent 2d9630243f
commit e960bdd49f
15 changed files with 135 additions and 131 deletions

View file

@ -1,3 +1,7 @@
# 2.2.0
* Pass `client` to `exchangeAuthorizationCodeForToken`.
* Apply `package:pedantic`.
# 2.1.0 # 2.1.0
* Updates * Updates
* Support `device_code` grants. * Support `device_code` grants.

View file

@ -39,7 +39,7 @@ but that it can also verify its identity via a `client_secret`.
```dart ```dart
class _Server extends AuthorizationServer<PseudoApplication, Map> { class _Server extends AuthorizationServer<PseudoApplication, Map> {
final Uuid _uuid = new Uuid(); final Uuid _uuid = Uuid();
@override @override
FutureOr<PseudoApplication> findClient(String clientId) { FutureOr<PseudoApplication> findClient(String clientId) {
@ -80,7 +80,7 @@ Future<AuthorizationCodeResponse> exchangeAuthCodeForAccessToken(
String redirectUri, String redirectUri,
RequestContext req, RequestContext req,
ResponseContext res) async { ResponseContext res) async {
return new AuthorizationCodeResponse('foo', refreshToken: 'bar'); return AuthorizationCodeResponse('foo', refreshToken: 'bar');
} }
``` ```
@ -137,7 +137,7 @@ Future requestAuthorizationCode(
// At this point, store `pkce.codeChallenge` and `pkce.codeChallengeMethod`, // At this point, store `pkce.codeChallenge` and `pkce.codeChallengeMethod`,
// so that when it's time to exchange the auth code for a token, we can // so that when it's time to exchange the auth code for a token, we can
// create a new [Pkce] object, and verify the client. // create a [Pkce] object, and verify the client.
return await getAuthCodeSomehow(client, pkce.codeChallenge, pkce.codeChallengeMethod); return await getAuthCodeSomehow(client, pkce.codeChallenge, pkce.codeChallengeMethod);
} }
@ -159,14 +159,14 @@ Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
var codeChallenge = await getTheChallenge(); var codeChallenge = await getTheChallenge();
var codeChallengeMethod = await getTheChallengeMethod(); var codeChallengeMethod = await getTheChallengeMethod();
// Make a new [Pkce] object. // Make a [Pkce] object.
var pkce = new Pkce(codeChallengeMethod, codeChallenge); var pkce = Pkce(codeChallengeMethod, codeChallenge);
// Call `validate`. If the client is invalid, it throws an OAuth2 exception. // Call `validate`. If the client is invalid, it throws an OAuth2 exception.
pkce.validate(codeVerifier); pkce.validate(codeVerifier);
// If we reach here, we know that the `code_verifier` was valid, // If we reach here, we know that the `code_verifier` was valid,
// so we can return our authorization token as per usual. // so we can return our authorization token as per usual.
return new AuthorizationTokenResponse('...'); return AuthorizationTokenResponse('...');
} }
``` ```

View file

@ -1,3 +1,8 @@
include: package:pedantic/analysis_options.yaml
analyzer: analyzer:
strong-mode: strong-mode:
implicit-casts: false implicit-casts: false
linter:
rules:
- unnecessary_const
- unnecessary_new

View file

@ -4,9 +4,9 @@ import 'package:angel_framework/angel_framework.dart';
import 'package:angel_oauth2/angel_oauth2.dart'; import 'package:angel_oauth2/angel_oauth2.dart';
main() async { main() async {
var app = new Angel(); var app = Angel();
var oauth2 = new _ExampleAuthorizationServer(); var oauth2 = _ExampleAuthorizationServer();
var _rgxBearer = new RegExp(r'^[Bb]earer ([^\n\s]+)$'); var _rgxBearer = RegExp(r'^[Bb]earer ([^\n\s]+)$');
app.group('/auth', (router) { app.group('/auth', (router) {
router router
@ -40,13 +40,13 @@ class _ExampleAuthorizationServer
@override @override
FutureOr<ThirdPartyApp> findClient(String clientId) { FutureOr<ThirdPartyApp> findClient(String clientId) {
// TODO: Add your code to find the app associated with a client ID. // TODO: Add your code to find the app associated with a client ID.
throw new UnimplementedError(); throw UnimplementedError();
} }
@override @override
FutureOr<bool> verifyClient(ThirdPartyApp client, String clientSecret) { FutureOr<bool> verifyClient(ThirdPartyApp client, String clientSecret) {
// TODO: Add your code to verify a client secret, if given one. // TODO: Add your code to verify a client secret, if given one.
throw new UnimplementedError(); throw UnimplementedError();
} }
@override @override
@ -58,17 +58,18 @@ class _ExampleAuthorizationServer
RequestContext req, RequestContext req,
ResponseContext res) { ResponseContext res) {
// TODO: In many cases, here you will render a view displaying to the user which scopes are being requested. // TODO: In many cases, here you will render a view displaying to the user which scopes are being requested.
throw new UnimplementedError(); throw UnimplementedError();
} }
@override @override
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken( FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
ThirdPartyApp client,
String authCode, String authCode,
String redirectUri, String redirectUri,
RequestContext req, RequestContext req,
ResponseContext res) { ResponseContext res) {
// TODO: Here, you'll convert the auth code into a full-fledged token. // TODO: Here, you'll convert the auth code into a full-fledged token.
// You might have the auth code stored in a database somewhere. // You might have the auth code stored in a database somewhere.
throw new UnimplementedError(); throw UnimplementedError();
} }
} }

View file

@ -52,7 +52,7 @@ class ErrorResponse {
/// The authorization request is still pending as the end user hasn't /// The authorization request is still pending as the end user hasn't
/// yet completed the user interaction steps (Section 3.3). The /// yet completed the user interaction steps (Section 3.3). The
/// client SHOULD repeat the Access Token Request to the token /// client SHOULD repeat the Access Token Request to the token
/// endpoint (a process known as polling). Before each new request /// endpoint (a process known as polling). Before each request
/// the client MUST wait at least the number of seconds specified by /// the client MUST wait at least the number of seconds specified by
/// the "interval" parameter of the Device Authorization Response (see /// the "interval" parameter of the Device Authorization Response (see
/// Section 3.2), or 5 seconds if none was provided, and respect any /// Section 3.2), or 5 seconds if none was provided, and respect any
@ -66,7 +66,7 @@ class ErrorResponse {
static const String slowDown = 'slow_down'; static const String slowDown = 'slow_down';
/// The "device_code" has expired and the device flow authorization /// The "device_code" has expired and the device flow authorization
/// session has concluded. The client MAY commence a new Device /// session has concluded. The client MAY commence a Device
/// Authorization Request but SHOULD wait for user interaction before /// Authorization Request but SHOULD wait for user interaction before
/// restarting to avoid unnecessary polling. /// restarting to avoid unnecessary polling.
static const String expiredToken = 'expired_token'; static const String expiredToken = 'expired_token';

View file

@ -23,20 +23,20 @@ class Pkce {
data['code_challenge_method']?.toString() ?? 'plain'; data['code_challenge_method']?.toString() ?? 'plain';
if (codeChallengeMethod != 'plain' && codeChallengeMethod != 's256') { if (codeChallengeMethod != 'plain' && codeChallengeMethod != 's256') {
throw new AuthorizationException(new ErrorResponse( throw AuthorizationException(ErrorResponse(
ErrorResponse.invalidRequest, ErrorResponse.invalidRequest,
"The `code_challenge_method` parameter must be either 'plain' or 's256'.", "The `code_challenge_method` parameter must be either 'plain' or 's256'.",
state, state,
uri: uri)); uri: uri));
} else if (codeChallenge?.isNotEmpty != true) { } else if (codeChallenge?.isNotEmpty != true) {
throw new AuthorizationException(new ErrorResponse( throw AuthorizationException(ErrorResponse(
ErrorResponse.invalidRequest, ErrorResponse.invalidRequest,
'Missing `code_challenge` parameter.', 'Missing `code_challenge` parameter.',
state, state,
uri: uri)); uri: uri));
} }
return new Pkce(codeChallengeMethod, codeChallenge); return Pkce(codeChallengeMethod, codeChallenge);
} }
/// Returns [true] if the [codeChallengeMethod] is `plain`. /// Returns [true] if the [codeChallengeMethod] is `plain`.
@ -57,8 +57,8 @@ class Pkce {
} }
if (foreignChallenge != codeChallenge) { if (foreignChallenge != codeChallenge) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse(ErrorResponse.invalidGrant, ErrorResponse(ErrorResponse.invalidGrant,
"The given `code_verifier` parameter is invalid.", state, "The given `code_verifier` parameter is invalid.", state,
uri: uri), uri: uri),
); );

View file

@ -11,7 +11,7 @@ typedef FutureOr<AuthorizationTokenResponse> ExtensionGrant(
RequestContext req, ResponseContext res); RequestContext req, ResponseContext res);
Future<String> _getParam(RequestContext req, String name, String state, Future<String> _getParam(RequestContext req, String name, String state,
{bool body: false, bool throwIfEmpty: true}) async { {bool body = false, bool throwIfEmpty = true}) async {
Map<String, dynamic> data; Map<String, dynamic> data;
if (body == true) { if (body == true) {
@ -23,8 +23,8 @@ Future<String> _getParam(RequestContext req, String name, String state,
var value = data.containsKey(name) ? data[name]?.toString() : null; var value = data.containsKey(name) ? data[name]?.toString() : null;
if (value?.isNotEmpty != true && throwIfEmpty) { if (value?.isNotEmpty != true && throwIfEmpty) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.invalidRequest, ErrorResponse.invalidRequest,
'Missing required parameter "$name".', 'Missing required parameter "$name".',
state, state,
@ -37,7 +37,7 @@ Future<String> _getParam(RequestContext req, String name, String state,
} }
Future<Iterable<String>> _getScopes(RequestContext req, Future<Iterable<String>> _getScopes(RequestContext req,
{bool body: false}) async { {bool body = false}) async {
Map<String, dynamic> data; Map<String, dynamic> data;
if (body == true) { if (body == true) {
@ -67,23 +67,19 @@ abstract class AuthorizationServer<Client, User> {
/// Retrieves the PKCE `code_verifier` parameter from a [RequestContext], or throws. /// Retrieves the PKCE `code_verifier` parameter from a [RequestContext], or throws.
Future<String> getPkceCodeVerifier(RequestContext req, Future<String> getPkceCodeVerifier(RequestContext req,
{bool body: true, String state, Uri uri}) async { {bool body = true, String state, Uri uri}) async {
var data = body var data = body
? await req.parseBody().then((_) => req.bodyAsMap) ? await req.parseBody().then((_) => req.bodyAsMap)
: req.queryParameters; : req.queryParameters;
var codeVerifier = data['code_verifier']; var codeVerifier = data['code_verifier'];
if (codeVerifier == null) { if (codeVerifier == null) {
throw new AuthorizationException(new ErrorResponse( throw AuthorizationException(ErrorResponse(ErrorResponse.invalidRequest,
ErrorResponse.invalidRequest, "Missing `code_verifier` parameter.", state,
"Missing `code_verifier` parameter.",
state,
uri: uri)); uri: uri));
} else if (codeVerifier is! String) { } else if (codeVerifier is! String) {
throw new AuthorizationException(new ErrorResponse( throw AuthorizationException(ErrorResponse(ErrorResponse.invalidRequest,
ErrorResponse.invalidRequest, "The `code_verifier` parameter must be a string.", state,
"The `code_verifier` parameter must be a string.",
state,
uri: uri)); uri: uri));
} else { } else {
return codeVerifier as String; return codeVerifier as String;
@ -100,8 +96,8 @@ abstract class AuthorizationServer<Client, User> {
String state, String state,
RequestContext req, RequestContext req,
ResponseContext res) { ResponseContext res) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Authorization code grants are not supported.', 'Authorization code grants are not supported.',
state, state,
@ -121,8 +117,8 @@ abstract class AuthorizationServer<Client, User> {
String state, String state,
RequestContext req, RequestContext req,
ResponseContext res) { ResponseContext res) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Authorization code grants are not supported.', 'Authorization code grants are not supported.',
state, state,
@ -133,12 +129,13 @@ abstract class AuthorizationServer<Client, User> {
/// Exchanges an authorization code for an authorization token. /// Exchanges an authorization code for an authorization token.
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken( FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
Client client,
String authCode, String authCode,
String redirectUri, String redirectUri,
RequestContext req, RequestContext req,
ResponseContext res) { ResponseContext res) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Authorization code grants are not supported.', 'Authorization code grants are not supported.',
req.uri.queryParameters['state'] ?? '', req.uri.queryParameters['state'] ?? '',
@ -155,8 +152,8 @@ abstract class AuthorizationServer<Client, User> {
RequestContext req, RequestContext req,
ResponseContext res) async { ResponseContext res) async {
var body = await req.parseBody().then((_) => req.bodyAsMap); var body = await req.parseBody().then((_) => req.bodyAsMap);
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Refreshing authorization tokens is not supported.', 'Refreshing authorization tokens is not supported.',
body['state']?.toString() ?? '', body['state']?.toString() ?? '',
@ -174,8 +171,8 @@ abstract class AuthorizationServer<Client, User> {
RequestContext req, RequestContext req,
ResponseContext res) async { ResponseContext res) async {
var body = await req.parseBody().then((_) => req.bodyAsMap); var body = await req.parseBody().then((_) => req.bodyAsMap);
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Resource owner password credentials grants are not supported.', 'Resource owner password credentials grants are not supported.',
body['state']?.toString() ?? '', body['state']?.toString() ?? '',
@ -188,8 +185,8 @@ abstract class AuthorizationServer<Client, User> {
FutureOr<AuthorizationTokenResponse> clientCredentialsGrant( FutureOr<AuthorizationTokenResponse> clientCredentialsGrant(
Client client, RequestContext req, ResponseContext res) async { Client client, RequestContext req, ResponseContext res) async {
var body = await req.parseBody().then((_) => req.bodyAsMap); var body = await req.parseBody().then((_) => req.bodyAsMap);
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Client credentials grants are not supported.', 'Client credentials grants are not supported.',
body['state']?.toString() ?? '', body['state']?.toString() ?? '',
@ -202,8 +199,8 @@ abstract class AuthorizationServer<Client, User> {
FutureOr<DeviceCodeResponse> requestDeviceCode(Client client, FutureOr<DeviceCodeResponse> requestDeviceCode(Client client,
Iterable<String> scopes, RequestContext req, ResponseContext res) async { Iterable<String> scopes, RequestContext req, ResponseContext res) async {
var body = await req.parseBody().then((_) => req.bodyAsMap); var body = await req.parseBody().then((_) => req.bodyAsMap);
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Device code grants are not supported.', 'Device code grants are not supported.',
body['state']?.toString() ?? '', body['state']?.toString() ?? '',
@ -220,8 +217,8 @@ abstract class AuthorizationServer<Client, User> {
RequestContext req, RequestContext req,
ResponseContext res) async { ResponseContext res) async {
var body = await req.parseBody().then((_) => req.bodyAsMap); var body = await req.parseBody().then((_) => req.bodyAsMap);
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unsupportedResponseType, ErrorResponse.unsupportedResponseType,
'Device code grants are not supported.', 'Device code grants are not supported.',
body['state']?.toString() ?? '', body['state']?.toString() ?? '',
@ -241,19 +238,18 @@ abstract class AuthorizationServer<Client, User> {
var responseType = await _getParam(req, 'response_type', state); var responseType = await _getParam(req, 'response_type', state);
req.container.registerLazySingleton<Pkce>((_) { req.container.registerLazySingleton<Pkce>((_) {
return new Pkce.fromJson(req.queryParameters, state: state); return Pkce.fromJson(req.queryParameters, state: state);
}); });
if (responseType == 'code') { if (responseType == 'code') {
// Ensure client ID // Ensure client ID
// TODO: Handle confidential clients
var clientId = await _getParam(req, 'client_id', state); var clientId = await _getParam(req, 'client_id', state);
// Find client // Find client
var client = await findClient(clientId); var client = await findClient(clientId);
if (client == null) { if (client == null) {
throw new AuthorizationException(new ErrorResponse( throw AuthorizationException(ErrorResponse(
ErrorResponse.unauthorizedClient, ErrorResponse.unauthorizedClient,
'Unknown client "$clientId".', 'Unknown client "$clientId".',
state, state,
@ -275,7 +271,7 @@ abstract class AuthorizationServer<Client, User> {
var client = await findClient(clientId); var client = await findClient(clientId);
if (client == null) { if (client == null) {
throw new AuthorizationException(new ErrorResponse( throw AuthorizationException(ErrorResponse(
ErrorResponse.unauthorizedClient, ErrorResponse.unauthorizedClient,
'Unknown client "$clientId".', 'Unknown client "$clientId".',
state, state,
@ -307,8 +303,8 @@ abstract class AuthorizationServer<Client, User> {
if (token.scope != null) if (token.scope != null)
queryParameters['scope'] = token.scope.join(' '); queryParameters['scope'] = token.scope.join(' ');
var fragment = queryParameters.keys var fragment =
.fold<StringBuffer>(new StringBuffer(), (buf, k) { queryParameters.keys.fold<StringBuffer>(StringBuffer(), (buf, k) {
if (buf.isNotEmpty) buf.write('&'); if (buf.isNotEmpty) buf.write('&');
return buf return buf
..write( ..write(
@ -320,8 +316,8 @@ abstract class AuthorizationServer<Client, User> {
res.redirect(target.toString()); res.redirect(target.toString());
return false; return false;
} on FormatException { } on FormatException {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.invalidRequest, ErrorResponse.invalidRequest,
'Invalid URI provided as "redirect_uri" parameter', 'Invalid URI provided as "redirect_uri" parameter',
state, state,
@ -330,8 +326,8 @@ abstract class AuthorizationServer<Client, User> {
} }
} }
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.invalidRequest, ErrorResponse.invalidRequest,
'Invalid or no "response_type" parameter provided', 'Invalid or no "response_type" parameter provided',
state, state,
@ -340,8 +336,8 @@ abstract class AuthorizationServer<Client, User> {
} on AngelHttpException { } on AngelHttpException {
rethrow; rethrow;
} catch (e, st) { } catch (e, st) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.serverError, ErrorResponse.serverError,
_internalServerError, _internalServerError,
state, state,
@ -353,8 +349,8 @@ abstract class AuthorizationServer<Client, User> {
} }
} }
static final RegExp _rgxBasic = new RegExp(r'Basic ([^$]+)'); static final RegExp _rgxBasic = RegExp(r'Basic ([^$]+)');
static final RegExp _rgxBasicAuth = new RegExp(r'([^:]*):([^$]*)'); static final RegExp _rgxBasicAuth = RegExp(r'([^:]*):([^$]*)');
/// A request handler that either exchanges authorization codes for authorization tokens, /// A request handler that either exchanges authorization codes for authorization tokens,
/// or refreshes authorization tokens. /// or refreshes authorization tokens.
@ -369,7 +365,7 @@ abstract class AuthorizationServer<Client, User> {
state = body['state']?.toString() ?? ''; state = body['state']?.toString() ?? '';
req.container.registerLazySingleton<Pkce>((_) { req.container.registerLazySingleton<Pkce>((_) {
return new Pkce.fromJson(req.bodyAsMap, state: state); return Pkce.fromJson(req.bodyAsMap, state: state);
}); });
var grantType = await _getParam(req, 'grant_type', state, var grantType = await _getParam(req, 'grant_type', state,
@ -383,12 +379,12 @@ abstract class AuthorizationServer<Client, User> {
if (match != null) { if (match != null) {
match = _rgxBasicAuth match = _rgxBasicAuth
.firstMatch(new String.fromCharCodes(base64Url.decode(match[1]))); .firstMatch(String.fromCharCodes(base64Url.decode(match[1])));
} }
if (match == null) { if (match == null) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unauthorizedClient, ErrorResponse.unauthorizedClient,
'Invalid or no "Authorization" header.', 'Invalid or no "Authorization" header.',
state, state,
@ -400,8 +396,8 @@ abstract class AuthorizationServer<Client, User> {
client = await findClient(clientId); client = await findClient(clientId);
if (client == null) { if (client == null) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unauthorizedClient, ErrorResponse.unauthorizedClient,
'Invalid "client_id" parameter.', 'Invalid "client_id" parameter.',
state, state,
@ -411,8 +407,8 @@ abstract class AuthorizationServer<Client, User> {
} }
if (!await verifyClient(client, clientSecret)) { if (!await verifyClient(client, clientSecret)) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unauthorizedClient, ErrorResponse.unauthorizedClient,
'Invalid "client_secret" parameter.', 'Invalid "client_secret" parameter.',
state, state,
@ -428,7 +424,7 @@ abstract class AuthorizationServer<Client, User> {
var redirectUri = var redirectUri =
await _getParam(req, 'redirect_uri', state, body: true); await _getParam(req, 'redirect_uri', state, body: true);
response = await exchangeAuthorizationCodeForToken( response = await exchangeAuthorizationCodeForToken(
code, redirectUri, req, res); client, code, redirectUri, req, res);
} else if (grantType == 'refresh_token') { } else if (grantType == 'refresh_token') {
var refreshToken = var refreshToken =
await _getParam(req, 'refresh_token', state, body: true); await _getParam(req, 'refresh_token', state, body: true);
@ -446,7 +442,7 @@ abstract class AuthorizationServer<Client, User> {
if (response.refreshToken != null) { if (response.refreshToken != null) {
// Remove refresh token // Remove refresh token
response = new AuthorizationTokenResponse( response = AuthorizationTokenResponse(
response.accessToken, response.accessToken,
expiresIn: response.expiresIn, expiresIn: response.expiresIn,
scope: response.scope, scope: response.scope,
@ -460,8 +456,8 @@ abstract class AuthorizationServer<Client, User> {
client = await findClient(clientId); client = await findClient(clientId);
if (client == null) { if (client == null) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unauthorizedClient, ErrorResponse.unauthorizedClient,
'Invalid "client_id" parameter.', 'Invalid "client_id" parameter.',
state, state,
@ -479,8 +475,8 @@ abstract class AuthorizationServer<Client, User> {
client = await findClient(clientId); client = await findClient(clientId);
if (client == null) { if (client == null) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.unauthorizedClient, ErrorResponse.unauthorizedClient,
'Invalid "client_id" parameter.', 'Invalid "client_id" parameter.',
state, state,
@ -499,8 +495,8 @@ abstract class AuthorizationServer<Client, User> {
..addAll(response.toJson()); ..addAll(response.toJson());
} }
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.invalidRequest, ErrorResponse.invalidRequest,
'Invalid or no "grant_type" parameter provided', 'Invalid or no "grant_type" parameter provided',
state, state,
@ -510,8 +506,8 @@ abstract class AuthorizationServer<Client, User> {
} on AngelHttpException { } on AngelHttpException {
rethrow; rethrow;
} catch (e, st) { } catch (e, st) {
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.serverError, ErrorResponse.serverError,
_internalServerError, _internalServerError,
state, state,

View file

@ -2,7 +2,7 @@ name: angel_oauth2
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
description: A class containing handlers that can be used within Angel to build a spec-compliant OAuth 2.0 server. description: A class containing handlers that can be used within Angel to build a spec-compliant OAuth 2.0 server.
homepage: https://github.com/angel-dart/oauth2.git homepage: https://github.com/angel-dart/oauth2.git
version: 2.1.0 version: 2.2.0
environment: environment:
sdk: ">=2.0.0-dev <3.0.0" sdk: ">=2.0.0-dev <3.0.0"
dependencies: dependencies:
@ -14,5 +14,6 @@ dev_dependencies:
angel_test: ^2.0.0-alpha angel_test: ^2.0.0-alpha
logging: logging:
oauth2: ^1.0.0 oauth2: ^1.0.0
pedantic: ^1.0.0
test: ^1.0.0 test: ^1.0.0
uuid: ^1.0.0 uuid: ^1.0.0

View file

@ -17,11 +17,11 @@ main() {
TestClient testClient; TestClient testClient;
setUp(() async { setUp(() async {
app = new Angel(); app = Angel();
app.configuration['properties'] = app.configuration; app.configuration['properties'] = app.configuration;
app.container.registerSingleton(new AuthCodes()); app.container.registerSingleton(AuthCodes());
var server = new _Server(); var server = _Server();
app.group('/oauth2', (router) { app.group('/oauth2', (router) {
router router
@ -29,14 +29,14 @@ main() {
..post('/token', server.tokenEndpoint); ..post('/token', server.tokenEndpoint);
}); });
app.logger = new Logger('angel') app.logger = Logger('angel')
..onRecord.listen((rec) { ..onRecord.listen((rec) {
print(rec); print(rec);
if (rec.error != null) print(rec.error); if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace); if (rec.stackTrace != null) print(rec.stackTrace);
}); });
var http = new AngelHttp(app); var http = AngelHttp(app);
var s = await http.startServer(); var s = await http.startServer();
var url = 'http://${s.address.address}:${s.port}'; var url = 'http://${s.address.address}:${s.port}';
authorizationEndpoint = Uri.parse('$url/oauth2/authorize'); authorizationEndpoint = Uri.parse('$url/oauth2/authorize');
@ -52,7 +52,7 @@ main() {
group('auth code', () { group('auth code', () {
oauth2.AuthorizationCodeGrant createGrant() => oauth2.AuthorizationCodeGrant createGrant() =>
new oauth2.AuthorizationCodeGrant( oauth2.AuthorizationCodeGrant(
pseudoApplication.id, pseudoApplication.id,
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
@ -119,7 +119,7 @@ main() {
} }
class _Server extends AuthorizationServer<PseudoApplication, Map> { class _Server extends AuthorizationServer<PseudoApplication, Map> {
final Uuid _uuid = new Uuid(); final Uuid _uuid = Uuid();
@override @override
FutureOr<PseudoApplication> findClient(String clientId) { FutureOr<PseudoApplication> findClient(String clientId) {
@ -155,6 +155,7 @@ class _Server extends AuthorizationServer<PseudoApplication, Map> {
@override @override
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken( Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
PseudoApplication client,
String authCode, String authCode,
String redirectUri, String redirectUri,
RequestContext req, RequestContext req,
@ -162,7 +163,7 @@ class _Server extends AuthorizationServer<PseudoApplication, Map> {
var authCodes = req.container.make<AuthCodes>(); var authCodes = req.container.make<AuthCodes>();
var state = authCodes[authCode]; var state = authCodes[authCode];
var refreshToken = state == 'can_refresh' ? '${authCode}_refresh' : null; var refreshToken = state == 'can_refresh' ? '${authCode}_refresh' : null;
return new AuthorizationTokenResponse('${authCode}_access', return AuthorizationTokenResponse('${authCode}_access',
refreshToken: refreshToken); refreshToken: refreshToken);
} }
} }

View file

@ -11,8 +11,8 @@ main() {
TestClient client; TestClient client;
setUp(() async { setUp(() async {
var app = new Angel(); var app = Angel();
var oauth2 = new _AuthorizationServer(); var oauth2 = _AuthorizationServer();
app.group('/oauth2', (router) { app.group('/oauth2', (router) {
router router
@ -47,7 +47,7 @@ main() {
allOf( allOf(
hasStatus(200), hasStatus(200),
hasContentType('application/json'), hasContentType('application/json'),
hasValidBody(new Validator({ hasValidBody(Validator({
'token_type': equals('bearer'), 'token_type': equals('bearer'),
'access_token': equals('foo'), 'access_token': equals('foo'),
})), })),
@ -101,6 +101,6 @@ class _AuthorizationServer
@override @override
Future<AuthorizationTokenResponse> clientCredentialsGrant( Future<AuthorizationTokenResponse> clientCredentialsGrant(
PseudoApplication client, RequestContext req, ResponseContext res) async { PseudoApplication client, RequestContext req, ResponseContext res) async {
return new AuthorizationTokenResponse('foo'); return AuthorizationTokenResponse('foo');
} }
} }

View file

@ -1,5 +1,5 @@
const PseudoApplication pseudoApplication = const PseudoApplication pseudoApplication =
const PseudoApplication('foo', 'bar', 'http://foo.bar/baz'); PseudoApplication('foo', 'bar', 'http://foo.bar/baz');
class PseudoApplication { class PseudoApplication {
final String id, secret, redirectUri; final String id, secret, redirectUri;
@ -7,10 +7,10 @@ class PseudoApplication {
const PseudoApplication(this.id, this.secret, this.redirectUri); const PseudoApplication(this.id, this.secret, this.redirectUri);
} }
const List<PseudoUser> pseudoUsers = const [ const List<PseudoUser> pseudoUsers = [
const PseudoUser(username: 'foo', password: 'bar'), PseudoUser(username: 'foo', password: 'bar'),
const PseudoUser(username: 'michael', password: 'jackson'), PseudoUser(username: 'michael', password: 'jackson'),
const PseudoUser(username: 'jon', password: 'skeet'), PseudoUser(username: 'jon', password: 'skeet'),
]; ];
class PseudoUser { class PseudoUser {

View file

@ -11,8 +11,8 @@ main() {
TestClient client; TestClient client;
setUp(() async { setUp(() async {
var app = new Angel(); var app = Angel();
var oauth2 = new _AuthorizationServer(); var oauth2 = _AuthorizationServer();
app.group('/oauth2', (router) { app.group('/oauth2', (router) {
router router
@ -20,7 +20,7 @@ main() {
..post('/token', oauth2.tokenEndpoint); ..post('/token', oauth2.tokenEndpoint);
}); });
app.logger = new Logger('angel_oauth2') app.logger = Logger('angel_oauth2')
..onRecord.listen((rec) { ..onRecord.listen((rec) {
print(rec); print(rec);
if (rec.error != null) print(rec.error); if (rec.error != null) print(rec.error);
@ -145,7 +145,7 @@ class _AuthorizationServer
@override @override
FutureOr<DeviceCodeResponse> requestDeviceCode(PseudoApplication client, FutureOr<DeviceCodeResponse> requestDeviceCode(PseudoApplication client,
Iterable<String> scopes, RequestContext req, ResponseContext res) { Iterable<String> scopes, RequestContext req, ResponseContext res) {
return new DeviceCodeResponse( return DeviceCodeResponse(
'foo', 'foo',
'bar', 'bar',
Uri.parse('https://regiostech.com') Uri.parse('https://regiostech.com')
@ -161,12 +161,12 @@ class _AuthorizationServer
RequestContext req, RequestContext req,
ResponseContext res) { ResponseContext res) {
if (deviceCode == 'brute') { if (deviceCode == 'brute') {
throw new AuthorizationException(new ErrorResponse( throw AuthorizationException(ErrorResponse(
ErrorResponse.slowDown, ErrorResponse.slowDown,
"Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!", "Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!",
state)); state));
} }
return new AuthorizationTokenResponse('foo'); return AuthorizationTokenResponse('foo');
} }
} }

View file

@ -10,8 +10,8 @@ main() {
TestClient client; TestClient client;
setUp(() async { setUp(() async {
var app = new Angel(); var app = Angel();
var oauth2 = new _AuthorizationServer(); var oauth2 = _AuthorizationServer();
app.group('/oauth2', (router) { app.group('/oauth2', (router) {
router router
@ -65,6 +65,6 @@ class _AuthorizationServer
String state, String state,
RequestContext req, RequestContext req,
ResponseContext res) async { ResponseContext res) async {
return new AuthorizationTokenResponse('foo'); return AuthorizationTokenResponse('foo');
} }
} }

View file

@ -12,8 +12,8 @@ main() {
Uri tokenEndpoint; Uri tokenEndpoint;
setUp(() async { setUp(() async {
app = new Angel(); app = Angel();
var auth = new _AuthorizationServer(); var auth = _AuthorizationServer();
app.group('/oauth2', (router) { app.group('/oauth2', (router) {
router router
@ -25,9 +25,9 @@ main() {
res.json(e.toJson()); res.json(e.toJson());
}; };
app.logger = new Logger('password_test')..onRecord.listen(print); app.logger = Logger('password_test')..onRecord.listen(print);
var http = new AngelHttp(app); var http = AngelHttp(app);
var server = await http.startServer(); var server = await http.startServer();
var url = 'http://${server.address.address}:${server.port}'; var url = 'http://${server.address.address}:${server.port}';
tokenEndpoint = Uri.parse('$url/oauth2/token'); tokenEndpoint = Uri.parse('$url/oauth2/token');
@ -61,7 +61,7 @@ main() {
secret: 'bar', secret: 'bar',
); );
throw new StateError('should fail'); throw StateError('should fail');
} on oauth2.AuthorizationException catch (e) { } on oauth2.AuthorizationException catch (e) {
expect(e.error, ErrorResponse.accessDenied); expect(e.error, ErrorResponse.accessDenied);
} finally { } finally {
@ -105,7 +105,7 @@ class _AuthorizationServer
Iterable<String> scopes, Iterable<String> scopes,
RequestContext req, RequestContext req,
ResponseContext res) async { ResponseContext res) async {
return new AuthorizationTokenResponse('baz', refreshToken: 'bar'); return AuthorizationTokenResponse('baz', refreshToken: 'bar');
} }
@override @override
@ -122,8 +122,8 @@ class _AuthorizationServer
if (user == null) { if (user == null) {
var body = await req.parseBody().then((_) => req.bodyAsMap); var body = await req.parseBody().then((_) => req.bodyAsMap);
throw new AuthorizationException( throw AuthorizationException(
new ErrorResponse( ErrorResponse(
ErrorResponse.accessDenied, ErrorResponse.accessDenied,
'Invalid username or password.', 'Invalid username or password.',
body['state']?.toString() ?? '', body['state']?.toString() ?? '',
@ -132,6 +132,6 @@ class _AuthorizationServer
); );
} }
return new AuthorizationTokenResponse('foo', refreshToken: 'bar'); return AuthorizationTokenResponse('foo', refreshToken: 'bar');
} }
} }

View file

@ -6,21 +6,19 @@ import 'package:angel_framework/http.dart';
import 'package:angel_oauth2/angel_oauth2.dart'; import 'package:angel_oauth2/angel_oauth2.dart';
import 'package:angel_test/angel_test.dart'; import 'package:angel_test/angel_test.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:oauth2/oauth2.dart' as oauth2;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:uuid/uuid.dart';
import 'common.dart'; import 'common.dart';
main() { main() {
Angel app; Angel app;
Uri authorizationEndpoint, tokenEndpoint, redirectUri; Uri authorizationEndpoint, tokenEndpoint;
TestClient testClient; TestClient testClient;
setUp(() async { setUp(() async {
app = new Angel(); app = Angel();
app.container.registerSingleton(new AuthCodes()); app.container.registerSingleton(AuthCodes());
var server = new _Server(); var server = _Server();
app.group('/oauth2', (router) { app.group('/oauth2', (router) {
router router
@ -28,19 +26,18 @@ main() {
..post('/token', server.tokenEndpoint); ..post('/token', server.tokenEndpoint);
}); });
app.logger = new Logger('angel') app.logger = Logger('angel')
..onRecord.listen((rec) { ..onRecord.listen((rec) {
print(rec); print(rec);
if (rec.error != null) print(rec.error); if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace); if (rec.stackTrace != null) print(rec.stackTrace);
}); });
var http = new AngelHttp(app); var http = AngelHttp(app);
var s = await http.startServer(); var s = await http.startServer();
var url = 'http://${s.address.address}:${s.port}'; var url = 'http://${s.address.address}:${s.port}';
authorizationEndpoint = Uri.parse('$url/oauth2/authorize'); authorizationEndpoint = Uri.parse('$url/oauth2/authorize');
tokenEndpoint = Uri.parse('$url/oauth2/token'); tokenEndpoint = Uri.parse('$url/oauth2/token');
redirectUri = Uri.parse('http://foo.bar/baz');
testClient = await connectTo(app); testClient = await connectTo(app);
}); });
@ -228,8 +225,6 @@ main() {
} }
class _Server extends AuthorizationServer<PseudoApplication, Map> { class _Server extends AuthorizationServer<PseudoApplication, Map> {
final Uuid _uuid = new Uuid();
@override @override
FutureOr<PseudoApplication> findClient(String clientId) { FutureOr<PseudoApplication> findClient(String clientId) {
return pseudoApplication; return pseudoApplication;
@ -255,14 +250,15 @@ class _Server extends AuthorizationServer<PseudoApplication, Map> {
@override @override
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken( Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
PseudoApplication client,
String authCode, String authCode,
String redirectUri, String redirectUri,
RequestContext req, RequestContext req,
ResponseContext res) async { ResponseContext res) async {
var codeVerifier = await getPkceCodeVerifier(req); var codeVerifier = await getPkceCodeVerifier(req);
var pkce = new Pkce('plain', 'hello'); var pkce = Pkce('plain', 'hello');
pkce.validate(codeVerifier); pkce.validate(codeVerifier);
return new AuthorizationTokenResponse('yes'); return AuthorizationTokenResponse('yes');
} }
} }