import 'package:angel_framework/angel_framework.dart'; import 'package:angel_security/angel_security.dart'; import 'package:angel_security/hooks.dart' as hooks; import 'package:angel_test/angel_test.dart'; import 'package:crypto/crypto.dart'; import 'package:test/test.dart'; main() { Angel app; TestClient client; setUp(() async { app = Angel() ..fallback((req, res) async { var xUser = req.headers.value('X-User'); if (xUser != null) { req.container.registerSingleton( User(id: xUser, roles: xUser == 'John' ? ['foo:bar'] : [])); } return true; }); app ..use('/user_data', UserDataService()) ..use('/artists', ArtistService()) ..use('/roled', RoledService()); (app.findService('user_data') as HookedService) ..beforeIndexed.listen(hooks.queryWithCurrentUser()) ..beforeCreated.listen(hooks.hashPassword()); app.findService('artists') as HookedService ..beforeIndexed.listen(hooks.restrictToAuthenticated()) ..beforeRead.listen(hooks.restrictToOwner()) ..beforeCreated.listen(hooks.associateCurrentUser()); (app.findService('roled') as HookedService) ..beforeIndexed.listen(Permission('foo:*').toHook()) ..beforeRead.listen(Permission('foo:*').toHook(owner: true)); var errorHandler = app.errorHandler; app.errorHandler = (e, req, res) { print(e.toJson()); print(e.stackTrace); return errorHandler(e, req, res); }; client = await connectTo(app); }); tearDown(() => client.close()); group('associateCurrentUser', () { test('fail', () async { try { var response = await client.service('artists').create({'foo': 'bar'}); print(response); throw StateError('Creating without userId bad request'); } catch (e) { print(e); expect(e, const TypeMatcher()); var err = e as AngelHttpException; expect(err.statusCode, equals(403)); } }); test('succeed', () async { var response = await client .post('/artists', headers: {'X-User': 'John'}, body: {'foo': 'bar'}); print('Response: ${response.body}'); print('Status: ${response.statusCode}'); expect(response, allOf(hasStatus(201), isJson({'foo': 'bar'}))); }); }); group('queryWithCurrentUser', () { test('fail', () async { try { var response = await client.service('user_data').index(); print(response); throw StateError('Indexing without user forbidden'); } catch (e) { print(e); expect(e, const TypeMatcher()); var err = e as AngelHttpException; expect(err.statusCode, equals(403)); } }); test('succeed', () async { var response = await client.get('user_data', headers: {'X-User': 'John'}); print('Response: ${response.body}'); expect(response, allOf(hasStatus(200), isJson(['foo', 'bar']))); }); }); test('hashPassword', () async { var response = await client .service('user_data') .create({'username': 'foo', 'password': 'jdoe1'}); print('Response: ${response}'); expect(response, equals({'foo': 'bar'})); }); group('restrictToAuthenticated', () { test('fail', () async { try { var response = await client.service('artists').index(); print(response); throw StateError('Indexing without user forbidden'); } catch (e) { print(e); expect(e, const TypeMatcher()); var err = e as AngelHttpException; expect(err.statusCode, equals(403)); } }); test('succeed', () async { var response = await client.get('/artists', headers: {'X-User': 'John'}); print('Response: ${response.body}'); expect( response, allOf( hasStatus(200), isJson([ { "id": "king_of_pop", "userId": "John", "name": "Michael Jackson" }, {"id": "raymond", "userId": "Bob", "name": "Usher"} ]))); }); }); group('restrictToOwner', () { test('fail', () async { try { var response = await client.service('artists').read('king_of_pop'); print(response); throw StateError('Reading without owner forbidden'); } catch (e) { print(e); expect(e, const TypeMatcher()); var err = e as AngelHttpException; expect(err.statusCode, equals(401)); } }); test('succeed', () async { var response = await client.get('/artists/king_of_pop', headers: {'X-User': 'John'}); print('Response: ${response.body}'); expect( response, allOf( hasStatus(200), isJson({ "id": "king_of_pop", "userId": "John", "name": "Michael Jackson" }))); }); }); group('permission restrict', () { test('fail', () async { try { var response = await client.service('roled').index(); print(response); throw StateError('Reading without roles forbidden'); } catch (e) { print(e); expect(e, const TypeMatcher()); var err = e as AngelHttpException; expect(err.statusCode, equals(403)); } }); test('succeed', () async { var response = await client.get('/roled/king_of_pop', headers: {'X-User': 'John'}); print('Response: ${response.body}'); expect( response, allOf( hasStatus(200), isJson({ "id": "king_of_pop", "userId": "John", "name": "Michael Jackson" }))); }); test('owner', () async { var response = await client.get('/roled/raymond', headers: {'X-User': 'Bob'}); print('Response: ${response.body}'); expect( response, allOf(hasStatus(200), isJson({"id": "raymond", "userId": "Bob", "name": "Usher"}))); }); }); } class User { String id; List roles; User({this.id, this.roles = const []}); } class UserDataService extends Service { static const Map _data = const { 'John': const ['foo', 'bar'] }; @override index([Map params]) async { print('Params: $params'); if (params?.containsKey('query') != true) throw AngelHttpException.badRequest(message: 'query required'); String name = params['query']['userId']?.toString(); if (!_data.containsKey(name)) throw AngelHttpException.notFound( message: "No data found for user '$name'."); return _data[name]; } @override create(data, [Map params]) async { if (data is! Map || !data.containsKey('password')) throw AngelHttpException.badRequest(message: 'Required password!'); var expected = String.fromCharCodes(sha256.convert('jdoe1'.codeUnits).bytes); if (data['password'] != (expected)) throw AngelHttpException.conflict(message: 'Passwords do not match.'); return {'foo': 'bar'}; } } class ArtistService extends Service { static const List _ARTISTS = const [_MICHAEL_JACKSON, _USHER]; @override index([params]) async => _ARTISTS; @override read(id, [params]) async => _ARTISTS.firstWhere((a) => a.id == id); @override create(data, [params]) async { if (data is! Map || !data.containsKey('userId')) throw AngelHttpException.badRequest(message: 'Required userId'); return {'foo': 'bar'}; } } class Artist { final String id, userId, name; const Artist({this.id, this.userId, this.name}); } const Artist _USHER = const Artist(id: 'raymond', userId: 'Bob', name: 'Usher'); const Artist _MICHAEL_JACKSON = const Artist(id: 'king_of_pop', userId: 'John', name: 'Michael Jackson'); class RoledService extends Service { @override index([params]) async { return ['foo']; } @override read(id, [params]) async => ArtistService._ARTISTS.firstWhere((a) => a.id == id); }