platform/packages/oauth2/test/pkce_test.dart

284 lines
8.2 KiB
Dart
Raw Normal View History

2018-12-15 08:39:04 +00:00
import 'dart:async';
import 'dart:collection';
2023-10-08 03:29:28 +00:00
import 'package:angel3_container/mirrors.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';
import 'package:angel3_oauth2/angel3_oauth2.dart';
import 'package:angel3_test/angel3_test.dart';
2018-12-15 08:39:04 +00:00
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'common.dart';
void main() {
2018-12-15 08:39:04 +00:00
Angel app;
late Uri authorizationEndpoint, tokenEndpoint;
late TestClient testClient;
2018-12-15 08:39:04 +00:00
setUp(() async {
2023-10-08 03:29:28 +00:00
app = Angel(reflector: MirrorsReflector());
2022-04-25 00:54:13 +00:00
app.container.registerSingleton(AuthCodes());
2018-12-15 08:39:04 +00:00
2019-05-02 07:28:38 +00:00
var server = _Server();
2018-12-15 08:39:04 +00:00
app.group('/oauth2', (router) {
router
..get('/authorize', server.authorizationEndpoint)
..post('/token', server.tokenEndpoint);
});
2019-05-02 07:28:38 +00:00
app.logger = Logger('angel')
2018-12-15 08:39:04 +00:00
..onRecord.listen((rec) {
print(rec);
if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace);
});
2019-05-02 07:28:38 +00:00
var http = AngelHttp(app);
2018-12-15 08:39:04 +00:00
var s = await http.startServer();
var url = 'http://${s.address.address}:${s.port}';
authorizationEndpoint = Uri.parse('$url/oauth2/authorize');
tokenEndpoint = Uri.parse('$url/oauth2/token');
testClient = await connectTo(app);
});
tearDown(() async {
await testClient.close();
});
group('get auth code', () {
test('with challenge + implied plain', () async {
var url = authorizationEndpoint.replace(queryParameters: {
'response_type': 'code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry',
'code_challenge': 'foo',
});
var response =
await testClient.get(url, headers: {'accept': 'application/json'});
2018-12-15 08:39:04 +00:00
print(response.body);
expect(
response,
allOf(
hasStatus(200),
isJson({'code': 'ok'}),
2018-12-15 08:39:04 +00:00
));
});
test('with challenge + plain', () async {
var url = authorizationEndpoint.replace(queryParameters: {
'response_type': 'code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry',
'code_challenge': 'foo',
'code_challenge_method': 'plain',
});
var response =
await testClient.get(url, headers: {'accept': 'application/json'});
2018-12-15 08:39:04 +00:00
print(response.body);
expect(
response,
allOf(
hasStatus(200),
isJson({'code': 'ok'}),
2018-12-15 08:39:04 +00:00
));
});
test('with challenge + s256', () async {
var url = authorizationEndpoint.replace(queryParameters: {
'response_type': 'code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry',
'code_challenge': 'foo',
'code_challenge_method': 's256',
});
var response =
await testClient.get(url, headers: {'accept': 'application/json'});
2018-12-15 08:39:04 +00:00
print(response.body);
expect(
response,
allOf(
hasStatus(200),
isJson({'code': 'ok'}),
2018-12-15 08:39:04 +00:00
));
});
test('with challenge + wrong method', () async {
var url = authorizationEndpoint.replace(queryParameters: {
'response_type': 'code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry',
'code_challenge': 'foo',
'code_challenge_method': 'bar',
});
var response =
await testClient.get(url, headers: {'accept': 'application/json'});
2018-12-15 08:39:04 +00:00
print(response.body);
expect(
response,
allOf(
hasStatus(400),
isJson({
'error': 'invalid_request',
'error_description':
2018-12-15 08:39:04 +00:00
"The `code_challenge_method` parameter must be either 'plain' or 's256'."
}),
));
});
test('with no challenge', () async {
var url = authorizationEndpoint.replace(queryParameters: {
'response_type': 'code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry'
});
var response =
await testClient.get(url, headers: {'accept': 'application/json'});
2018-12-15 08:39:04 +00:00
print(response.body);
expect(
response,
allOf(
hasStatus(400),
isJson({
'error': 'invalid_request',
'error_description': 'Missing `code_challenge` parameter.'
2018-12-15 08:39:04 +00:00
}),
));
});
});
group('get token', () {
test('with correct verifier', () async {
var url = tokenEndpoint.replace(
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
var response = await testClient.post(url, headers: {
2018-12-15 08:39:04 +00:00
'accept': 'application/json',
2019-05-03 07:29:59 +00:00
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
2018-12-15 08:39:04 +00:00
}, body: {
'grant_type': 'authorization_code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry',
'code': 'ok',
'code_verifier': 'hello',
});
print(response.body);
expect(
response,
allOf(
hasStatus(200),
isJson({'token_type': 'bearer', 'access_token': 'yes'}),
2018-12-15 08:39:04 +00:00
));
});
test('with incorrect verifier', () async {
var url = tokenEndpoint.replace(
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
var response = await testClient.post(url, headers: {
2018-12-15 08:39:04 +00:00
'accept': 'application/json',
2019-05-03 07:29:59 +00:00
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
2018-12-15 08:39:04 +00:00
}, body: {
'grant_type': 'authorization_code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry',
'code': 'ok',
'code_verifier': 'foo',
});
print(response.body);
expect(
response,
allOf(
hasStatus(400),
isJson({
'error': 'invalid_grant',
'error_description':
'The given `code_verifier` parameter is invalid.'
2018-12-15 08:39:04 +00:00
}),
));
});
test('with missing verifier', () async {
var url = tokenEndpoint.replace(
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
var response = await testClient.post(url, headers: {
2018-12-15 08:39:04 +00:00
'accept': 'application/json',
2019-05-03 07:29:59 +00:00
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
2018-12-15 08:39:04 +00:00
}, body: {
'grant_type': 'authorization_code',
'client_id': 'freddie mercury',
'redirect_uri': 'https://freddie.mercu.ry',
'code': 'ok'
});
print(response.body);
expect(
response,
allOf(
hasStatus(400),
isJson({
'error': 'invalid_request',
'error_description': 'Missing `code_verifier` parameter.'
2018-12-15 08:39:04 +00:00
}),
));
});
});
}
class _Server extends AuthorizationServer<PseudoApplication, Map> {
@override
FutureOr<PseudoApplication> findClient(String? clientId) {
2018-12-15 08:39:04 +00:00
return pseudoApplication;
}
@override
Future<bool> verifyClient(
PseudoApplication client, String? clientSecret) async {
2018-12-15 08:39:04 +00:00
return client.secret == clientSecret;
}
@override
Future requestAuthorizationCode(
PseudoApplication client,
String? redirectUri,
2018-12-15 08:39:04 +00:00
Iterable<String> scopes,
String state,
RequestContext req,
2019-05-03 07:24:24 +00:00
ResponseContext res,
bool implicit) async {
req.container!.make<Pkce>();
2018-12-15 08:39:04 +00:00
return {'code': 'ok'};
}
@override
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
PseudoApplication? client,
String? authCode,
String? redirectUri,
2018-12-15 08:39:04 +00:00
RequestContext req,
ResponseContext res) async {
var codeVerifier = await getPkceCodeVerifier(req);
2019-05-02 07:28:38 +00:00
var pkce = Pkce('plain', 'hello');
2018-12-15 08:39:04 +00:00
pkce.validate(codeVerifier);
2019-05-02 07:28:38 +00:00
return AuthorizationTokenResponse('yes');
2018-12-15 08:39:04 +00:00
}
}
2023-05-27 01:33:53 +00:00
class AuthCodes with MapMixin<String, String> {
2018-12-15 08:39:04 +00:00
var inner = <String, String>{};
@override
String? operator [](Object? key) => inner[key as String];
2018-12-15 08:39:04 +00:00
@override
void operator []=(String key, String value) => inner[key] = value;
@override
void clear() => inner.clear();
@override
Iterable<String> get keys => inner.keys;
@override
String? remove(Object? key) => inner.remove(key);
2018-12-15 08:39:04 +00:00
}