2017-09-29 02:16:44 +00:00
|
|
|
import 'dart:async';
|
2018-11-08 15:32:36 +00:00
|
|
|
import 'dart:collection';
|
2018-11-08 15:34:49 +00:00
|
|
|
import 'dart:convert';
|
2024-10-13 01:45:27 +00:00
|
|
|
import 'package:protevus_framework/protevus_framework.dart';
|
|
|
|
import 'package:protevus_framework/http.dart';
|
|
|
|
import 'package:protevus_oauth2/protevus_oauth2.dart';
|
|
|
|
import 'package:protevus_test/protevus_test.dart';
|
2017-09-29 02:16:44 +00:00
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:oauth2/oauth2.dart' as oauth2;
|
|
|
|
import 'package:test/test.dart';
|
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
import 'common.dart';
|
|
|
|
|
2021-05-30 00:46:13 +00:00
|
|
|
void main() {
|
2024-10-12 10:35:14 +00:00
|
|
|
Protevus app;
|
2021-05-30 00:46:13 +00:00
|
|
|
late Uri authorizationEndpoint, tokenEndpoint, redirectUri;
|
|
|
|
late TestClient testClient;
|
2017-09-29 02:16:44 +00:00
|
|
|
|
|
|
|
setUp(() async {
|
2024-10-12 10:35:14 +00:00
|
|
|
app = Protevus();
|
2017-09-29 02:16:44 +00:00
|
|
|
app.configuration['properties'] = app.configuration;
|
2022-04-25 00:54:13 +00:00
|
|
|
app.container.registerSingleton(AuthCodes());
|
2017-09-29 02:16:44 +00:00
|
|
|
|
2019-05-02 07:28:38 +00:00
|
|
|
var server = _Server();
|
2017-09-29 02:16:44 +00:00
|
|
|
|
|
|
|
app.group('/oauth2', (router) {
|
|
|
|
router
|
|
|
|
..get('/authorize', server.authorizationEndpoint)
|
|
|
|
..post('/token', server.tokenEndpoint);
|
|
|
|
});
|
|
|
|
|
2024-10-12 10:35:14 +00:00
|
|
|
app.logger = Logger('protevus')
|
2017-09-29 02:16:44 +00:00
|
|
|
..onRecord.listen((rec) {
|
|
|
|
print(rec);
|
|
|
|
if (rec.error != null) print(rec.error);
|
|
|
|
if (rec.stackTrace != null) print(rec.stackTrace);
|
|
|
|
});
|
|
|
|
|
2024-10-12 10:35:14 +00:00
|
|
|
var http = ProtevusHttp(app);
|
2018-07-09 15:38:29 +00:00
|
|
|
var s = await http.startServer();
|
|
|
|
var url = 'http://${s.address.address}:${s.port}';
|
2017-09-29 02:16:44 +00:00
|
|
|
authorizationEndpoint = Uri.parse('$url/oauth2/authorize');
|
|
|
|
tokenEndpoint = Uri.parse('$url/oauth2/token');
|
|
|
|
redirectUri = Uri.parse('http://foo.bar/baz');
|
|
|
|
|
|
|
|
testClient = await connectTo(app);
|
|
|
|
});
|
|
|
|
|
|
|
|
tearDown(() async {
|
|
|
|
await testClient.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
group('auth code', () {
|
|
|
|
oauth2.AuthorizationCodeGrant createGrant() =>
|
2019-05-02 07:28:38 +00:00
|
|
|
oauth2.AuthorizationCodeGrant(
|
2017-09-29 02:16:44 +00:00
|
|
|
pseudoApplication.id,
|
|
|
|
authorizationEndpoint,
|
|
|
|
tokenEndpoint,
|
|
|
|
secret: pseudoApplication.secret,
|
|
|
|
);
|
|
|
|
|
|
|
|
test('show authorization form', () async {
|
|
|
|
var grant = createGrant();
|
|
|
|
var url = grant.getAuthorizationUrl(redirectUri, state: 'hello');
|
2022-01-05 04:03:02 +00:00
|
|
|
var response = await testClient.client.get(url);
|
2017-09-29 02:16:44 +00:00
|
|
|
print('Body: ${response.body}');
|
|
|
|
expect(
|
|
|
|
response.body,
|
2018-07-09 15:38:29 +00:00
|
|
|
json.encode(
|
2017-09-29 02:16:44 +00:00
|
|
|
'Hello ${pseudoApplication.id}:${pseudoApplication.secret}'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test('preserves state', () async {
|
|
|
|
var grant = createGrant();
|
|
|
|
var url = grant.getAuthorizationUrl(redirectUri, state: 'goodbye');
|
2022-01-05 04:03:02 +00:00
|
|
|
var response = await testClient.client.get(url);
|
2017-09-29 02:16:44 +00:00
|
|
|
print('Body: ${response.body}');
|
2018-07-09 15:38:29 +00:00
|
|
|
expect(json.decode(response.body)['state'], 'goodbye');
|
2017-09-29 02:16:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
test('sends auth code', () async {
|
|
|
|
var grant = createGrant();
|
|
|
|
var url = grant.getAuthorizationUrl(redirectUri);
|
2022-01-05 04:03:02 +00:00
|
|
|
var response = await testClient.client.get(url);
|
2017-09-29 02:16:44 +00:00
|
|
|
print('Body: ${response.body}');
|
|
|
|
expect(
|
2018-07-09 15:38:29 +00:00
|
|
|
json.decode(response.body),
|
2017-09-29 02:16:44 +00:00
|
|
|
allOf(
|
|
|
|
isMap,
|
|
|
|
predicate((Map m) => m.containsKey('code'), 'contains "code"'),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('exchange code for token', () async {
|
|
|
|
var grant = createGrant();
|
|
|
|
var url = grant.getAuthorizationUrl(redirectUri);
|
2022-01-05 04:03:02 +00:00
|
|
|
var response = await testClient.client.get(url);
|
2017-09-29 02:16:44 +00:00
|
|
|
print('Body: ${response.body}');
|
|
|
|
|
2018-11-08 15:32:36 +00:00
|
|
|
var authCode = json.decode(response.body)['code'].toString();
|
2017-09-29 02:16:44 +00:00
|
|
|
var client = await grant.handleAuthorizationCode(authCode);
|
2022-08-27 07:52:28 +00:00
|
|
|
expect(client.credentials.accessToken, '${authCode}_access');
|
2017-09-29 02:16:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
test('can send refresh token', () async {
|
|
|
|
var grant = createGrant();
|
|
|
|
var url = grant.getAuthorizationUrl(redirectUri, state: 'can_refresh');
|
2022-01-05 04:03:02 +00:00
|
|
|
var response = await testClient.client.get(url);
|
2017-09-29 02:16:44 +00:00
|
|
|
print('Body: ${response.body}');
|
|
|
|
|
2018-11-08 15:32:36 +00:00
|
|
|
var authCode = json.decode(response.body)['code'].toString();
|
2017-09-29 02:16:44 +00:00
|
|
|
var client = await grant.handleAuthorizationCode(authCode);
|
2022-08-27 07:52:28 +00:00
|
|
|
expect(client.credentials.accessToken, '${authCode}_access');
|
2017-09-29 02:16:44 +00:00
|
|
|
expect(client.credentials.canRefresh, isTrue);
|
2022-08-27 07:52:28 +00:00
|
|
|
expect(client.credentials.refreshToken, '${authCode}_refresh');
|
2017-09-29 02:16:44 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-16 06:38:46 +00:00
|
|
|
class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
2019-05-02 07:28:38 +00:00
|
|
|
final Uuid _uuid = Uuid();
|
2017-09-29 02:16:44 +00:00
|
|
|
|
|
|
|
@override
|
2021-05-30 00:46:13 +00:00
|
|
|
FutureOr<PseudoApplication>? findClient(String? clientId) {
|
2017-09-29 02:16:44 +00:00
|
|
|
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<bool> verifyClient(
|
2021-05-30 00:46:13 +00:00
|
|
|
PseudoApplication client, String? clientSecret) async {
|
2017-09-29 02:16:44 +00:00
|
|
|
return client.secret == clientSecret;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2017-10-16 06:38:46 +00:00
|
|
|
Future requestAuthorizationCode(
|
2017-09-29 02:16:44 +00:00
|
|
|
PseudoApplication client,
|
2021-05-30 00:46:13 +00:00
|
|
|
String? redirectUri,
|
2017-09-29 02:16:44 +00:00
|
|
|
Iterable<String> scopes,
|
|
|
|
String state,
|
|
|
|
RequestContext req,
|
2019-05-03 07:24:24 +00:00
|
|
|
ResponseContext res,
|
|
|
|
bool implicit) async {
|
|
|
|
if (implicit) {
|
|
|
|
// Throw the default error on an implicit grant attempt.
|
|
|
|
return super.requestAuthorizationCode(
|
|
|
|
client, redirectUri, scopes, state, req, res, implicit);
|
|
|
|
}
|
|
|
|
|
2021-05-30 00:46:13 +00:00
|
|
|
if (state == 'hello') {
|
2017-09-29 02:16:44 +00:00
|
|
|
return 'Hello ${pseudoApplication.id}:${pseudoApplication.secret}';
|
2021-05-30 00:46:13 +00:00
|
|
|
}
|
2017-09-29 02:16:44 +00:00
|
|
|
|
2019-05-02 07:31:02 +00:00
|
|
|
var authCode = _uuid.v4();
|
2022-01-05 04:03:02 +00:00
|
|
|
var authCodes = req.container!.make<AuthCodes>();
|
2017-09-29 02:16:44 +00:00
|
|
|
authCodes[authCode] = state;
|
|
|
|
|
|
|
|
res.headers['content-type'] = 'application/json';
|
|
|
|
var result = {'code': authCode};
|
2021-05-30 00:46:13 +00:00
|
|
|
if (state.isNotEmpty == true) result['state'] = state;
|
2017-09-29 02:16:44 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2017-10-16 06:38:46 +00:00
|
|
|
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
2021-05-30 00:46:13 +00:00
|
|
|
PseudoApplication? client,
|
|
|
|
String? authCode,
|
|
|
|
String? redirectUri,
|
2017-09-29 02:16:44 +00:00
|
|
|
RequestContext req,
|
|
|
|
ResponseContext res) async {
|
2022-01-05 04:03:02 +00:00
|
|
|
var authCodes = req.container!.make<AuthCodes>();
|
2021-05-30 00:46:13 +00:00
|
|
|
var state = authCodes[authCode!];
|
2017-09-29 02:16:44 +00:00
|
|
|
var refreshToken = state == 'can_refresh' ? '${authCode}_refresh' : null;
|
2019-05-02 07:28:38 +00:00
|
|
|
return AuthorizationTokenResponse('${authCode}_access',
|
2017-09-29 02:16:44 +00:00
|
|
|
refreshToken: refreshToken);
|
|
|
|
}
|
|
|
|
}
|
2018-11-08 15:32:36 +00:00
|
|
|
|
2023-05-27 01:33:53 +00:00
|
|
|
class AuthCodes with MapMixin<String, String> {
|
2018-11-08 15:32:36 +00:00
|
|
|
var inner = <String, String>{};
|
|
|
|
|
|
|
|
@override
|
2021-05-30 00:46:13 +00:00
|
|
|
String? operator [](Object? key) => inner[key as String];
|
2018-11-08 15:32:36 +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
|
2021-05-30 00:46:13 +00:00
|
|
|
String? remove(Object? key) => inner.remove(key);
|
2018-11-08 15:32:36 +00:00
|
|
|
}
|