Upgrades for 2.0, fix static errors
This commit is contained in:
parent
ec7bff58c1
commit
fcbcc6963d
27 changed files with 244 additions and 253 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -26,4 +26,5 @@ doc/api/
|
|||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
|
||||
log.txt
|
||||
log.txt
|
||||
.dart_tool
|
2
CHANGELOG.md
Normal file
2
CHANGELOG.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# 2.0.0
|
||||
* Angel 2 updates.
|
10
README.md
10
README.md
|
@ -41,7 +41,7 @@ app.before.add(banIp('1.2.3.*'));
|
|||
app.before.add(banIp('1.2.*.4'));
|
||||
|
||||
// Or multiple filters:
|
||||
app.before.add(banIp(['1.2.3.4', '192.*.*.*', new RegExp(r'1\.2.\3.\4')]));
|
||||
app.before.add(banIp(['1.2.3.4', '192.*.*.*', RegExp(r'1\.2.\3.\4')]));
|
||||
|
||||
// Also can ban origins
|
||||
app.before.add(banOrigin('*.known-attacker.com'));
|
||||
|
@ -64,7 +64,7 @@ Throws a `429` error if the given rate limit is exceeded.
|
|||
|
||||
```dart
|
||||
// Example: 5 requests per minute
|
||||
app.before.add(throttleRequests(5, new Duration(minutes: 1)));
|
||||
app.before.add(throttleRequests(5, Duration(minutes: 1)));
|
||||
```
|
||||
|
||||
# Helmet
|
||||
|
@ -101,11 +101,11 @@ They take the form of:
|
|||
The specifics are up to you.
|
||||
|
||||
```dart
|
||||
var permission = new Permission('admin | users:find');
|
||||
var permission = Permission('admin | users:find');
|
||||
|
||||
// Or:
|
||||
// PermissionBuilders support + and | operators. Operands can be Strings, Permissions or PermissionBuilders.
|
||||
var permission = (new PermissionBuilder('admin') | (new PermissionBuilder('users') + 'find')).toPermission();
|
||||
var permission = (PermissionBuilder('admin') | (PermissionBuilder('users') + 'find')).toPermission();
|
||||
|
||||
// Transform into middleware
|
||||
app.chain(permission.toMiddleware()).get('/protected', ...);
|
||||
|
@ -118,6 +118,6 @@ app.service('protected').beforeModify(permission.toHook());
|
|||
//
|
||||
// `variantPermission` is included in the `package:angel_security/hooks.dart` library.
|
||||
app.service('posts').beforeModify(variantPermission((e) {
|
||||
return new PermissionBuilder('posts:modify:${e.id}');
|
||||
return PermissionBuilder('posts:modify:${e.id}');
|
||||
}));
|
||||
```
|
|
@ -1,2 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode: true
|
||||
strong-mode:
|
||||
implicit-casts: true
|
1
example/main.dart
Normal file
1
example/main.dart
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -9,8 +9,8 @@ import 'package:angel_framework/angel_framework.dart';
|
|||
/// String can take the following formats:
|
||||
/// 1. 1.2.3.4
|
||||
/// 2. 1.2.3.*, 1.2.*.*, etc.
|
||||
RequestMiddleware banIp(filter,
|
||||
{String message:
|
||||
RequestHandler banIp(filter,
|
||||
{String message =
|
||||
'Your IP address is forbidden from accessing this server.'}) {
|
||||
var filters = [];
|
||||
Iterable inputs = filter is Iterable ? filter : [filter];
|
||||
|
@ -22,10 +22,10 @@ RequestMiddleware banIp(filter,
|
|||
if (!input.contains('*'))
|
||||
filters.add(input);
|
||||
else {
|
||||
filters.add(new RegExp(input.replaceAll('*', '[0-9]+')));
|
||||
filters.add(RegExp(input.replaceAll('*', '[0-9]+')));
|
||||
}
|
||||
} else
|
||||
throw new ArgumentError('Cannot use $input as an IP filter.');
|
||||
throw ArgumentError('Cannot use $input as an IP filter.');
|
||||
}
|
||||
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
|
@ -43,7 +43,7 @@ RequestMiddleware banIp(filter,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!check()) throw new AngelHttpException.forbidden(message: message);
|
||||
if (!check()) throw AngelHttpException.forbidden(message: message);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
@ -56,9 +56,9 @@ RequestMiddleware banIp(filter,
|
|||
/// String can take the following formats:
|
||||
/// 1. example.com
|
||||
/// 2. *.example.com, a.b.*.d.e.f, etc.
|
||||
RequestMiddleware banOrigin(filter,
|
||||
{String message: 'You are forbidden from accessing this server.',
|
||||
bool allowEmptyOrigin: false}) {
|
||||
RequestHandler banOrigin(filter,
|
||||
{String message = 'You are forbidden from accessing this server.',
|
||||
bool allowEmptyOrigin = false}) {
|
||||
var filters = [];
|
||||
Iterable inputs = filter is Iterable ? filter : [filter];
|
||||
|
||||
|
@ -69,17 +69,17 @@ RequestMiddleware banOrigin(filter,
|
|||
if (!input.contains('*'))
|
||||
filters.add(input);
|
||||
else {
|
||||
filters.add(new RegExp(input.replaceAll('*', '[^\.]+')));
|
||||
filters.add(RegExp(input.replaceAll('*', '[^\.]+')));
|
||||
}
|
||||
} else
|
||||
throw new ArgumentError('Cannot use $input as an origin filter.');
|
||||
throw ArgumentError('Cannot use $input as an origin filter.');
|
||||
}
|
||||
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
var origin = req.headers.value('origin');
|
||||
|
||||
if ((origin == null || origin.isEmpty) && !allowEmptyOrigin)
|
||||
throw new AngelHttpException.badRequest(
|
||||
throw AngelHttpException.badRequest(
|
||||
message: "'Origin' header is required.");
|
||||
|
||||
bool check() {
|
||||
|
@ -92,7 +92,7 @@ RequestMiddleware banOrigin(filter,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!check()) throw new AngelHttpException.forbidden(message: message);
|
||||
if (!check()) throw AngelHttpException.forbidden(message: message);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,20 +2,21 @@ import 'dart:io';
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
final Uuid _uuid = new Uuid();
|
||||
final Uuid _uuid = Uuid();
|
||||
|
||||
/// Ensures that the request contains a correct CSRF token.
|
||||
RequestMiddleware verifyCsrfToken(
|
||||
{bool allowCookie: false,
|
||||
bool allowQuery: true,
|
||||
String name: 'csrf_token'}) {
|
||||
RequestHandler verifyCsrfToken(
|
||||
{bool allowCookie = false,
|
||||
bool allowQuery = true,
|
||||
String name = 'csrf_token'}) {
|
||||
return (RequestContext req, res) async {
|
||||
String csrfToken;
|
||||
|
||||
if (allowQuery && (await req.lazyQuery()).containsKey(name))
|
||||
csrfToken = req.query[name];
|
||||
else if ((await req.lazyBody()).containsKey(name))
|
||||
csrfToken = req.body[name];
|
||||
if (allowQuery && req.queryParameters.containsKey(name))
|
||||
csrfToken = req.queryParameters[name];
|
||||
else if ((await req.parseBody().then((_) => req.bodyAsMap))
|
||||
.containsKey(name))
|
||||
csrfToken = req.bodyAsMap[name];
|
||||
else if (allowCookie) {
|
||||
var cookie =
|
||||
req.cookies.firstWhere((c) => c.name == name, orElse: () => null);
|
||||
|
@ -23,22 +24,22 @@ RequestMiddleware verifyCsrfToken(
|
|||
}
|
||||
|
||||
if (csrfToken == null || !req.session.containsKey(name))
|
||||
throw new AngelHttpException.badRequest(message: 'Missing CSRF token.');
|
||||
throw AngelHttpException.badRequest(message: 'Missing CSRF token.');
|
||||
|
||||
String correctToken = req.session[name];
|
||||
|
||||
if (csrfToken != correctToken)
|
||||
throw new AngelHttpException.badRequest(message: 'Invalid CSRF token.');
|
||||
throw AngelHttpException.badRequest(message: 'Invalid CSRF token.');
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/// Adds a CSRF token to the session, if none is present.
|
||||
RequestHandler setCsrfToken({String name: 'csrf_token', bool cookie: false}) {
|
||||
RequestHandler setCsrfToken({String name = 'csrf_token', bool cookie = false}) {
|
||||
return (RequestContext req, res) async {
|
||||
if (!req.session.containsKey(name)) req.session[name] = _uuid.v4();
|
||||
if (cookie) res.cookies.add(new Cookie(name, req.session[name]));
|
||||
if (cookie) res.cookies.add(Cookie(name, req.session[name]));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
|
||||
/// Adds the authed user to `e.params`, only if present in `req.injections`.
|
||||
HookedServiceEventListener addUserToParams({String as, userKey}) {
|
||||
return (HookedServiceEvent e) {
|
||||
var user = e.request?.grab(userKey ?? 'user');
|
||||
|
||||
/// Adds the authed user to `e.params`, only if present in `req.container`.
|
||||
HookedServiceEventListener addUserToParams<User>({String as}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
var user = await e.request?.container?.makeAsync<User>();
|
||||
if (user != null) e.params[as ?? 'user'] = user;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'errors.dart';
|
||||
|
@ -7,55 +8,51 @@ import 'is_server_side.dart';
|
|||
///
|
||||
///Default [idField] is `'id'`.
|
||||
/// Default [ownerField] is `'userId'`.
|
||||
/// Default [userKey] is `'user'`.
|
||||
HookedServiceEventListener associateCurrentUser(
|
||||
HookedServiceEventListener associateCurrentUser<Id, Data, User>(
|
||||
{String idField,
|
||||
String ownerField,
|
||||
userKey,
|
||||
String errorMessage,
|
||||
bool allowNullUserId: false,
|
||||
getId(user),
|
||||
assignUserId(id, obj)}) {
|
||||
bool allowNullUserId = false,
|
||||
FutureOr<Id> Function(User) getId,
|
||||
FutureOr<Data> Function(Id, Data) assignUserId}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
var fieldName = ownerField?.isNotEmpty == true ? ownerField : 'userId';
|
||||
var user = e.request?.grab(userKey ?? 'user');
|
||||
var user = await e.request?.container?.makeAsync<User>();
|
||||
|
||||
if (user == null) {
|
||||
if (!isServerSide(e))
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: errorMessage ?? Errors.NOT_LOGGED_IN);
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
_getId(user) {
|
||||
Future<Id> _getId(User user) async {
|
||||
if (getId != null)
|
||||
return getId(user);
|
||||
return await getId(user);
|
||||
else if (user is Map)
|
||||
return user[idField ?? 'id'];
|
||||
else if (idField == null || idField == 'id')
|
||||
return user.id;
|
||||
else
|
||||
return reflect(user).getField(new Symbol(idField ?? 'id')).reflectee;
|
||||
return reflect(user).getField(Symbol(idField ?? 'id')).reflectee;
|
||||
}
|
||||
|
||||
var id = await _getId(user);
|
||||
|
||||
if (id == null && allowNullUserId != true)
|
||||
throw new AngelHttpException.notProcessable(
|
||||
throw AngelHttpException.notProcessable(
|
||||
message: 'Current user is missing a $fieldName field.');
|
||||
|
||||
_assignUserId(id, obj) {
|
||||
Future<Data> _assignUserId(Id id, Data obj) async {
|
||||
if (assignUserId != null)
|
||||
return assignUserId(id, obj);
|
||||
else if (obj is Map)
|
||||
obj[fieldName] = id;
|
||||
else if (fieldName == 'userId')
|
||||
obj.userId = id;
|
||||
else
|
||||
reflect(obj).setField(new Symbol(fieldName), id);
|
||||
return obj..[fieldName] = id;
|
||||
else {
|
||||
reflect(obj).setField(Symbol(fieldName), id);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
await _assignUserId(id, e.data);
|
||||
e.data = await _assignUserId(id, e.data);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ HookedServiceEventListener hashPassword(
|
|||
return user?.password;
|
||||
else
|
||||
return reflect(user)
|
||||
.getField(new Symbol(passwordField ?? 'password'))
|
||||
.getField(Symbol(passwordField ?? 'password'))
|
||||
.reflectee;
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,7 @@ HookedServiceEventListener hashPassword(
|
|||
else if (user is Map)
|
||||
user[passwordField ?? 'password'] = password;
|
||||
else
|
||||
reflect(user)
|
||||
.setField(new Symbol(passwordField ?? 'password'), password);
|
||||
reflect(user).setField(Symbol(passwordField ?? 'password'), password);
|
||||
}
|
||||
|
||||
if (e.data != null) {
|
||||
|
@ -44,7 +43,7 @@ HookedServiceEventListener hashPassword(
|
|||
|
||||
if (password != null) {
|
||||
var digest = h.convert(password.codeUnits);
|
||||
return _setPassword(new String.fromCharCodes(digest.bytes), user);
|
||||
return _setPassword(String.fromCharCodes(digest.bytes), user);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'errors.dart';
|
||||
|
@ -7,20 +8,19 @@ import 'is_server_side.dart';
|
|||
///
|
||||
/// Default [as] is `'userId'`.
|
||||
/// Default [userKey] is `'user'`.
|
||||
HookedServiceEventListener queryWithCurrentUser(
|
||||
HookedServiceEventListener queryWithCurrentUser<Id, User>(
|
||||
{String as,
|
||||
String idField,
|
||||
userKey,
|
||||
String errorMessage,
|
||||
bool allowNullUserId: false,
|
||||
getId(user)}) {
|
||||
bool allowNullUserId = false,
|
||||
FutureOr<Id> Function(User) getId}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
var fieldName = idField?.isNotEmpty == true ? idField : 'id';
|
||||
var user = e.request?.grab(userKey ?? 'user');
|
||||
var user = await e.request?.container?.makeAsync<User>();
|
||||
|
||||
if (user == null) {
|
||||
if (!isServerSide(e))
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: errorMessage ?? Errors.NOT_LOGGED_IN);
|
||||
else
|
||||
return;
|
||||
|
@ -34,13 +34,13 @@ HookedServiceEventListener queryWithCurrentUser(
|
|||
else if (fieldName == 'id')
|
||||
return user.id;
|
||||
else
|
||||
return reflect(user).getField(new Symbol(fieldName)).reflectee;
|
||||
return reflect(user).getField(Symbol(fieldName)).reflectee;
|
||||
}
|
||||
|
||||
var id = await _getId(user);
|
||||
|
||||
if (id == null && allowNullUserId != true)
|
||||
throw new AngelHttpException.notProcessable(
|
||||
throw AngelHttpException.notProcessable(
|
||||
message: 'Current user is missing a \'$fieldName\' field.');
|
||||
|
||||
var data = {as?.isNotEmpty == true ? as : 'userId': id};
|
||||
|
|
|
@ -3,14 +3,14 @@ import 'errors.dart';
|
|||
import 'is_server_side.dart';
|
||||
|
||||
/// Restricts the service method to authed users only.
|
||||
HookedServiceEventListener restrictToAuthenticated(
|
||||
{userKey, String errorMessage}) {
|
||||
HookedServiceEventListener restrictToAuthenticated<User>(
|
||||
{String errorMessage}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
var user = e.request?.grab(userKey ?? 'user');
|
||||
var user = await e.request?.container?.makeAsync<User>();
|
||||
|
||||
if (user == null) {
|
||||
if (!isServerSide(e))
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: errorMessage ?? Errors.NOT_LOGGED_IN);
|
||||
else
|
||||
return;
|
||||
|
|
|
@ -1,64 +1,60 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'errors.dart';
|
||||
import 'is_server_side.dart';
|
||||
|
||||
/// Restricts users to accessing only their own resources.
|
||||
HookedServiceEventListener restrictToOwner(
|
||||
HookedServiceEventListener restrictToOwner<Id, Data, User>(
|
||||
{String idField,
|
||||
String ownerField,
|
||||
userKey,
|
||||
String errorMessage,
|
||||
getId(user),
|
||||
getOwner(obj)}) {
|
||||
FutureOr<Id> Function(User) getId,
|
||||
FutureOr<Id> Function(Data) getOwnerId}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
if (!isServerSide(e)) {
|
||||
var user = e.request?.grab(userKey ?? 'user');
|
||||
var user = await e.request?.container?.makeAsync<User>();
|
||||
|
||||
if (user == null)
|
||||
throw new AngelHttpException.notAuthenticated(
|
||||
throw AngelHttpException.notAuthenticated(
|
||||
message:
|
||||
'The current user is missing. You must not be authenticated.');
|
||||
|
||||
_getId(user) {
|
||||
Future<Id> _getId(User user) async {
|
||||
if (getId != null)
|
||||
return getId(user);
|
||||
else if (user is Map)
|
||||
return user[idField ?? 'id'];
|
||||
else if (idField == null || idField == 'id')
|
||||
return user.id;
|
||||
else
|
||||
return reflect(user).getField(new Symbol(idField ?? 'id')).reflectee;
|
||||
return reflect(user).getField(Symbol(idField ?? 'id')).reflectee;
|
||||
}
|
||||
|
||||
var id = await _getId(user);
|
||||
|
||||
if (id == null) throw new Exception('The current user has no ID.');
|
||||
if (id == null) throw Exception('The current user has no ID.');
|
||||
|
||||
var resource = await e.service.read(
|
||||
e.id,
|
||||
{}
|
||||
..addAll(e.params ?? {})
|
||||
..remove('provider'));
|
||||
..remove('provider')) as Data;
|
||||
|
||||
if (resource != null) {
|
||||
_getOwner(obj) {
|
||||
if (getOwner != null)
|
||||
return getOwner(obj);
|
||||
Future<Id> _getOwner(Data obj) async {
|
||||
if (getOwnerId != null)
|
||||
return await getOwnerId(obj);
|
||||
else if (obj is Map)
|
||||
return obj[ownerField ?? 'userId'];
|
||||
else if (ownerField == null || ownerField == 'userId')
|
||||
return obj.userId;
|
||||
return obj[ownerField ?? 'user_id'];
|
||||
else
|
||||
return reflect(obj)
|
||||
.getField(new Symbol(ownerField ?? 'userId'))
|
||||
.getField(Symbol(ownerField ?? 'userId'))
|
||||
.reflectee;
|
||||
}
|
||||
|
||||
var ownerId = await _getOwner(resource);
|
||||
|
||||
if ((ownerId is Iterable && !ownerId.contains(id)) || ownerId != id)
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: errorMessage ?? Errors.INSUFFICIENT_PERMISSIONS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ HookedServiceEventListener variantPermission(
|
|||
createPermission(HookedServiceEvent e),
|
||||
{String errorMessage,
|
||||
userKey,
|
||||
bool owner: false,
|
||||
bool owner = false,
|
||||
getRoles(user),
|
||||
getId(user),
|
||||
getOwner(obj)}) {
|
||||
|
@ -19,7 +19,7 @@ HookedServiceEventListener variantPermission(
|
|||
if (permission is PermissionBuilder) permission = permission.toPermission();
|
||||
|
||||
if (permission is! Permission)
|
||||
throw new ArgumentError(
|
||||
throw ArgumentError(
|
||||
'createPermission must generate a Permission, whether synchronously or asynchronously.');
|
||||
await permission.toHook(
|
||||
errorMessage: errorMessage,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'hooks/errors.dart';
|
||||
import 'hooks/restrict_to_owner.dart';
|
||||
|
@ -10,48 +12,42 @@ class Permission {
|
|||
|
||||
Permission(this.minimum);
|
||||
|
||||
call(RequestContext req, ResponseContext res) {
|
||||
return toMiddleware()(req, res);
|
||||
}
|
||||
|
||||
/// Creates a hook that restricts a service method to users with this
|
||||
/// permission, or if they are the resource [owner].
|
||||
///
|
||||
/// [getId] and [getOwner] are passed to [restrictToOwner], along with
|
||||
/// [idField], [ownerField], [userKey] and [errorMessage].
|
||||
HookedServiceEventListener toHook(
|
||||
HookedServiceEventListener toHook<Id, Data, User>(
|
||||
{String errorMessage,
|
||||
String idField,
|
||||
String ownerField,
|
||||
String userKey,
|
||||
bool owner: false,
|
||||
getRoles(user),
|
||||
getId(user),
|
||||
getOwner(obj)}) {
|
||||
bool owner = false,
|
||||
FutureOr<Iterable<String>> Function(User) getRoles,
|
||||
FutureOr<Id> Function(User) getId,
|
||||
FutureOr<Id> Function(Data) getOwnerId}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
if (e.params.containsKey('provider')) {
|
||||
var user = e.request?.grab(userKey ?? 'user');
|
||||
var user = await e.request?.container?.makeAsync<User>();
|
||||
|
||||
if (user == null)
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: errorMessage ?? Errors.INSUFFICIENT_PERMISSIONS);
|
||||
|
||||
var roleFinder = getRoles ?? (user) async => user.roles ?? [];
|
||||
List<String> roles = (await roleFinder(user)).toList();
|
||||
var roleFinder = getRoles ?? (user) => <String>[];
|
||||
var roles = (await roleFinder(user)).toList();
|
||||
|
||||
if (!roles.any(verify)) {
|
||||
// Try owner if the roles are not in-place
|
||||
if (owner == true) {
|
||||
var listener = restrictToOwner(
|
||||
var listener = restrictToOwner<Id, Data, User>(
|
||||
idField: idField,
|
||||
ownerField: ownerField,
|
||||
userKey: userKey,
|
||||
errorMessage: errorMessage,
|
||||
getId: getId,
|
||||
getOwner: getOwner);
|
||||
getOwnerId: getOwnerId);
|
||||
await listener(e);
|
||||
} else
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: errorMessage ?? Errors.INSUFFICIENT_PERMISSIONS);
|
||||
}
|
||||
}
|
||||
|
@ -59,13 +55,12 @@ class Permission {
|
|||
}
|
||||
|
||||
/// Restricts a route to users who have sufficient permissions.
|
||||
RequestMiddleware toMiddleware(
|
||||
{String message, String userKey, getRoles(user)}) {
|
||||
RequestHandler toMiddleware<User>({String message, getRoles(user)}) {
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
var user = req.grab(userKey ?? 'user');
|
||||
var user = await req.container.makeAsync<User>();
|
||||
|
||||
if (user == null)
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: message ??
|
||||
'You have insufficient permissions to perform this action.');
|
||||
|
||||
|
@ -73,7 +68,7 @@ class Permission {
|
|||
List<String> roles = (await roleFinder(user)).toList();
|
||||
|
||||
if (!roles.any(verify))
|
||||
throw new AngelHttpException.forbidden(
|
||||
throw AngelHttpException.forbidden(
|
||||
message: message ??
|
||||
'You have insufficient permissions to perform this action.');
|
||||
|
||||
|
@ -124,7 +119,7 @@ class PermissionBuilder {
|
|||
/// A minimum
|
||||
PermissionBuilder(this._min);
|
||||
|
||||
factory PermissionBuilder.wildcard() => new PermissionBuilder('*');
|
||||
factory PermissionBuilder.wildcard() => PermissionBuilder('*');
|
||||
|
||||
PermissionBuilder operator +(other) {
|
||||
if (other is String)
|
||||
|
@ -134,38 +129,36 @@ class PermissionBuilder {
|
|||
else if (other is Permission)
|
||||
return add(other.minimum);
|
||||
else
|
||||
throw new ArgumentError(
|
||||
throw ArgumentError(
|
||||
'Cannot add a ${other.runtimeType} to a PermissionBuilder.');
|
||||
}
|
||||
|
||||
PermissionBuilder operator |(other) {
|
||||
if (other is String)
|
||||
return or(new PermissionBuilder(other));
|
||||
return or(PermissionBuilder(other));
|
||||
else if (other is PermissionBuilder)
|
||||
return or(other);
|
||||
else if (other is Permission)
|
||||
return or(new PermissionBuilder(other.minimum));
|
||||
return or(PermissionBuilder(other.minimum));
|
||||
else
|
||||
throw new ArgumentError(
|
||||
throw ArgumentError(
|
||||
'Cannot or a ${other.runtimeType} and a PermissionBuilder.');
|
||||
}
|
||||
|
||||
call(RequestContext req, ResponseContext res) => toPermission()(req, res);
|
||||
|
||||
/// Adds another level of [constraint].
|
||||
PermissionBuilder add(String constraint) =>
|
||||
new PermissionBuilder('$_min:$constraint');
|
||||
PermissionBuilder('$_min:$constraint');
|
||||
|
||||
/// Adds a wildcard permission.
|
||||
PermissionBuilder allowAll() => add('*');
|
||||
|
||||
/// Duplicates this builder.
|
||||
PermissionBuilder clone() => new PermissionBuilder(_min);
|
||||
PermissionBuilder clone() => PermissionBuilder(_min);
|
||||
|
||||
/// Allows an alternative permission.
|
||||
PermissionBuilder or(PermissionBuilder other) =>
|
||||
new PermissionBuilder('$_min | ${other._min}');
|
||||
PermissionBuilder('$_min | ${other._min}');
|
||||
|
||||
/// Builds a [Permission].
|
||||
Permission toPermission() => new Permission(_min);
|
||||
Permission toPermission() => Permission(_min);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@ import 'dart:convert';
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
|
||||
final Map<Pattern, String> DEFAULT_SANITIZERS = {
|
||||
new RegExp(
|
||||
r'<\s*s\s*c\s*r\s*i\s*p\s*t\s*>.*<\s*\/\s*s\s*c\s*r\s*i\s*p\s*t\s*>',
|
||||
RegExp(r'<\s*s\s*c\s*r\s*i\s*p\s*t\s*>.*<\s*\/\s*s\s*c\s*r\s*i\s*p\s*t\s*>',
|
||||
caseSensitive: false): ''
|
||||
};
|
||||
|
||||
|
@ -12,19 +11,19 @@ final Map<Pattern, String> DEFAULT_SANITIZERS = {
|
|||
/// You can also provide a Map of patterns to [replace].
|
||||
///
|
||||
/// You can sanitize the [body] or [query] (both `true` by default).
|
||||
RequestMiddleware sanitizeHtmlInput(
|
||||
{bool body: true,
|
||||
bool query: true,
|
||||
Map<Pattern, String> replace: const {}}) {
|
||||
RequestHandler sanitizeHtmlInput(
|
||||
{bool body = true,
|
||||
bool query = true,
|
||||
Map<Pattern, String> replace = const {}}) {
|
||||
var sanitizers = {}..addAll(DEFAULT_SANITIZERS)..addAll(replace ?? {});
|
||||
|
||||
return (RequestContext req, res) async {
|
||||
return (req, res) async {
|
||||
if (body) {
|
||||
await req.parse();
|
||||
_sanitizeMap(req.body, sanitizers);
|
||||
await req.parseBody();
|
||||
_sanitizeMap(req.bodyAsMap, sanitizers);
|
||||
}
|
||||
|
||||
if (query) _sanitizeMap(req.query, sanitizers);
|
||||
if (query) _sanitizeMap(req.queryParameters, sanitizers);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
@ -37,7 +36,7 @@ _sanitize(v, Map<Pattern, String> sanitizers) {
|
|||
str = str.replaceAll(needle, replace);
|
||||
});
|
||||
|
||||
return HTML_ESCAPE.convert(str);
|
||||
return htmlEscape.convert(str);
|
||||
} else if (v is Map) {
|
||||
_sanitizeMap(v, sanitizers);
|
||||
return v;
|
||||
|
|
|
@ -7,8 +7,8 @@ import 'package:angel_framework/angel_framework.dart';
|
|||
/// The default is to identify requests by their source IP.
|
||||
///
|
||||
/// This works well attached to a `multiserver` instance.
|
||||
RequestMiddleware throttleRequests(int max, Duration duration,
|
||||
{String message: '429 Too Many Requests', identify(RequestContext req)}) {
|
||||
RequestHandler throttleRequests(int max, Duration duration,
|
||||
{String message = '429 Too Many Requests', identify(RequestContext req)}) {
|
||||
var identifyRequest = identify ?? (RequestContext req) async => req.ip;
|
||||
Map<String, int> table = {};
|
||||
Map<String, List<int>> times = {};
|
||||
|
@ -17,7 +17,7 @@ RequestMiddleware throttleRequests(int max, Duration duration,
|
|||
var id = (await identifyRequest(req)).toString();
|
||||
int currentCount;
|
||||
|
||||
var now = new DateTime.now().millisecondsSinceEpoch;
|
||||
var now = DateTime.now().millisecondsSinceEpoch;
|
||||
int firstVisit;
|
||||
|
||||
// If the user has visited within the given duration...
|
||||
|
@ -45,7 +45,7 @@ RequestMiddleware throttleRequests(int max, Duration duration,
|
|||
currentCount = table[id] = 1;
|
||||
|
||||
if (currentCount > max) {
|
||||
throw new AngelHttpException(null,
|
||||
throw AngelHttpException(null,
|
||||
statusCode: 429, message: message ?? '429 Too Many Requests');
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:angel_framework/angel_framework.dart';
|
|||
/// String can take the following formats:
|
||||
/// 1. 1.2.3.4
|
||||
/// 2. 1.2.3.*, 1.2.*.*, etc.
|
||||
RequestMiddleware trustProxy(filter) {
|
||||
RequestHandler trustProxy(filter) {
|
||||
var filters = [];
|
||||
Iterable inputs = filter is Iterable ? filter : [filter];
|
||||
|
||||
|
@ -21,10 +21,10 @@ RequestMiddleware trustProxy(filter) {
|
|||
if (!input.contains('*'))
|
||||
filters.add(input);
|
||||
else {
|
||||
filters.add(new RegExp(input.replaceAll('*', '[0-9]+')));
|
||||
filters.add(RegExp(input.replaceAll('*', '[0-9]+')));
|
||||
}
|
||||
} else
|
||||
throw new ArgumentError('Cannot use $input as a trusted proxy filter.');
|
||||
throw ArgumentError('Cannot use $input as a trusted proxy filter.');
|
||||
}
|
||||
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
|
@ -49,7 +49,8 @@ RequestMiddleware trustProxy(filter) {
|
|||
if (k.trim().toLowerCase().startsWith('x-forwarded')) headers[k] = v;
|
||||
});
|
||||
|
||||
req.inject(ForwardedClient, new _ForwardedClientImpl(headers));
|
||||
req.container
|
||||
.registerSingleton<ForwardedClient>(_ForwardedClientImpl(headers));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -78,5 +79,5 @@ class _ForwardedClientImpl extends ForwardedClient {
|
|||
|
||||
@override
|
||||
Map<String, List<String>> get headers =>
|
||||
new Map<String, List<String>>.unmodifiable(_headers);
|
||||
Map<String, List<String>>.unmodifiable(_headers);
|
||||
}
|
||||
|
|
29
pubspec.yaml
29
pubspec.yaml
|
@ -1,15 +1,16 @@
|
|||
author: "Tobe O <thosakwe@gmail.com>"
|
||||
description: "Angel middleware designed to enhance application security by patching common Web security holes."
|
||||
homepage: "https://github.com/angel-dart/security"
|
||||
name: "angel_security"
|
||||
version: "1.1.0"
|
||||
dependencies:
|
||||
angel_framework: "^1.1.0"
|
||||
dev_dependencies:
|
||||
angel_auth: "^1.1.0"
|
||||
angel_test: "^1.1.0"
|
||||
angel_validate: "^1.0.0"
|
||||
console: "^2.2.4"
|
||||
test: "^0.12.0"
|
||||
name: angel_security
|
||||
version: 2.0.0
|
||||
description: Angel infrastructure for improving security by patching common holes.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/security
|
||||
environment:
|
||||
sdk: ">=1.19.0"
|
||||
sdk: ">=2.0.0-dev <3.0.0"
|
||||
dependencies:
|
||||
angel_framework: ^2.0.0-alpha
|
||||
dev_dependencies:
|
||||
angel_auth: ^2.0.0
|
||||
angel_test: ^2.0.0
|
||||
angel_validate: ^2.0.0
|
||||
console: ^3.0.0
|
||||
pedantic: ^1.0.0
|
||||
test: ^1.0.0
|
||||
|
|
|
@ -8,10 +8,11 @@ main() {
|
|||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel()
|
||||
..chain(banIp('*.*.*.*')).get('/ban', 'WTF')
|
||||
..chain(banOrigin('*')).get('/ban-origin', 'WTF')
|
||||
..chain(banOrigin('*.foo.bar')).get('/allow-origin', 'YAY');
|
||||
app = Angel()
|
||||
..chain([banIp('*.*.*.*')]).get('/ban', (req, res) => 'WTF')
|
||||
..chain([banOrigin('*')]).get('/ban-origin', (req, res) => 'WTF')
|
||||
..chain([banOrigin('*.foo.bar')])
|
||||
.get('/allow-origin', (req, res) => 'YAY');
|
||||
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
|
|
@ -4,16 +4,16 @@ import 'package:angel_test/angel_test.dart';
|
|||
import 'package:http/http.dart' as http;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
final RegExp _sessId = new RegExp(r'DARTSESSID=([^;]+);');
|
||||
final RegExp _sessId = RegExp(r'DARTSESSID=([^;]+);');
|
||||
|
||||
main() async {
|
||||
Angel app;
|
||||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel()..responseFinalizers.add(setCsrfToken());
|
||||
app = Angel()..responseFinalizers.add(setCsrfToken());
|
||||
|
||||
app.chain(verifyCsrfToken()).get('/valid', 'Valid!');
|
||||
app.chain([verifyCsrfToken()]).get('/valid', (req, res) => 'Valid!');
|
||||
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
|
|
@ -10,33 +10,33 @@ main() {
|
|||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel()
|
||||
..lazyParseBodies = true
|
||||
..use((RequestContext req, res) async {
|
||||
app = Angel()
|
||||
..fallback((req, res) async {
|
||||
var xUser = req.headers.value('X-User');
|
||||
if (xUser != null)
|
||||
req.inject('user',
|
||||
new User(id: xUser, roles: xUser == 'John' ? ['foo:bar'] : []));
|
||||
if (xUser != null) {
|
||||
req.container.registerSingleton(
|
||||
User(id: xUser, roles: xUser == 'John' ? ['foo:bar'] : []));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
app
|
||||
..use('/user_data', new UserDataService())
|
||||
..use('/artists', new ArtistService())
|
||||
..use('/roled', new RoledService());
|
||||
..use('/user_data', UserDataService())
|
||||
..use('/artists', ArtistService())
|
||||
..use('/roled', RoledService());
|
||||
|
||||
(app.service('user_data') as HookedService)
|
||||
(app.findService('user_data') as HookedService)
|
||||
..beforeIndexed.listen(hooks.queryWithCurrentUser())
|
||||
..beforeCreated.listen(hooks.hashPassword());
|
||||
|
||||
app.service('artists') as HookedService
|
||||
app.findService('artists') as HookedService
|
||||
..beforeIndexed.listen(hooks.restrictToAuthenticated())
|
||||
..beforeRead.listen(hooks.restrictToOwner())
|
||||
..beforeCreated.listen(hooks.associateCurrentUser());
|
||||
|
||||
(app.service('roled') as HookedService)
|
||||
..beforeIndexed.listen(new Permission('foo:*').toHook())
|
||||
..beforeRead.listen(new Permission('foo:*').toHook(owner: true));
|
||||
(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) {
|
||||
|
@ -55,10 +55,10 @@ main() {
|
|||
try {
|
||||
var response = await client.service('artists').create({'foo': 'bar'});
|
||||
print(response);
|
||||
throw new StateError('Creating without userId bad request');
|
||||
throw StateError('Creating without userId bad request');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
expect(e, new isInstanceOf<AngelHttpException>());
|
||||
expect(e, const TypeMatcher<AngelHttpException>());
|
||||
var err = e as AngelHttpException;
|
||||
expect(err.statusCode, equals(403));
|
||||
}
|
||||
|
@ -78,10 +78,10 @@ main() {
|
|||
try {
|
||||
var response = await client.service('user_data').index();
|
||||
print(response);
|
||||
throw new StateError('Indexing without user forbidden');
|
||||
throw StateError('Indexing without user forbidden');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
expect(e, new isInstanceOf<AngelHttpException>());
|
||||
expect(e, const TypeMatcher<AngelHttpException>());
|
||||
var err = e as AngelHttpException;
|
||||
expect(err.statusCode, equals(403));
|
||||
}
|
||||
|
@ -107,10 +107,10 @@ main() {
|
|||
try {
|
||||
var response = await client.service('artists').index();
|
||||
print(response);
|
||||
throw new StateError('Indexing without user forbidden');
|
||||
throw StateError('Indexing without user forbidden');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
expect(e, new isInstanceOf<AngelHttpException>());
|
||||
expect(e, const TypeMatcher<AngelHttpException>());
|
||||
var err = e as AngelHttpException;
|
||||
expect(err.statusCode, equals(403));
|
||||
}
|
||||
|
@ -139,10 +139,10 @@ main() {
|
|||
try {
|
||||
var response = await client.service('artists').read('king_of_pop');
|
||||
print(response);
|
||||
throw new StateError('Reading without owner forbidden');
|
||||
throw StateError('Reading without owner forbidden');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
expect(e, new isInstanceOf<AngelHttpException>());
|
||||
expect(e, const TypeMatcher<AngelHttpException>());
|
||||
var err = e as AngelHttpException;
|
||||
expect(err.statusCode, equals(401));
|
||||
}
|
||||
|
@ -169,10 +169,10 @@ main() {
|
|||
try {
|
||||
var response = await client.service('roled').index();
|
||||
print(response);
|
||||
throw new StateError('Reading without roles forbidden');
|
||||
throw StateError('Reading without roles forbidden');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
expect(e, new isInstanceOf<AngelHttpException>());
|
||||
expect(e, const TypeMatcher<AngelHttpException>());
|
||||
var err = e as AngelHttpException;
|
||||
expect(err.statusCode, equals(403));
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ main() {
|
|||
class User {
|
||||
String id;
|
||||
List<String> roles;
|
||||
User({this.id, this.roles: const []});
|
||||
User({this.id, this.roles = const []});
|
||||
}
|
||||
|
||||
class UserDataService extends Service {
|
||||
|
@ -220,12 +220,12 @@ class UserDataService extends Service {
|
|||
index([Map params]) async {
|
||||
print('Params: $params');
|
||||
if (params?.containsKey('query') != true)
|
||||
throw new AngelHttpException.badRequest(message: 'query required');
|
||||
throw AngelHttpException.badRequest(message: 'query required');
|
||||
|
||||
String name = params['query']['userId']?.toString();
|
||||
|
||||
if (!_data.containsKey(name))
|
||||
throw new AngelHttpException.notFound(
|
||||
throw AngelHttpException.notFound(
|
||||
message: "No data found for user '$name'.");
|
||||
|
||||
return _data[name];
|
||||
|
@ -234,13 +234,13 @@ class UserDataService extends Service {
|
|||
@override
|
||||
create(data, [Map params]) async {
|
||||
if (data is! Map || !data.containsKey('password'))
|
||||
throw new AngelHttpException.badRequest(message: 'Required password!');
|
||||
throw AngelHttpException.badRequest(message: 'Required password!');
|
||||
|
||||
var expected =
|
||||
new String.fromCharCodes(sha256.convert('jdoe1'.codeUnits).bytes);
|
||||
String.fromCharCodes(sha256.convert('jdoe1'.codeUnits).bytes);
|
||||
|
||||
if (data['password'] != (expected))
|
||||
throw new AngelHttpException.conflict(message: 'Passwords do not match.');
|
||||
throw AngelHttpException.conflict(message: 'Passwords do not match.');
|
||||
return {'foo': 'bar'};
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ class ArtistService extends Service {
|
|||
@override
|
||||
create(data, [params]) async {
|
||||
if (data is! Map || !data.containsKey('userId'))
|
||||
throw new AngelHttpException.badRequest(message: 'Required userId');
|
||||
throw AngelHttpException.badRequest(message: 'Required userId');
|
||||
|
||||
return {'foo': 'bar'};
|
||||
}
|
||||
|
|
|
@ -14,36 +14,42 @@ main() {
|
|||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel();
|
||||
app = Angel();
|
||||
|
||||
app.use((RequestContext req, res) async {
|
||||
app.fallback((req, res) async {
|
||||
// In real life, you'd use auth to check user roles,
|
||||
// but in this case, let's just set the user manually
|
||||
var xRoles = req.headers.value('X-Roles');
|
||||
var xRoles = req.headers['X-Roles'];
|
||||
|
||||
if (xRoles?.isNotEmpty == true) {
|
||||
req.inject('user', new User(req.headers['X-Roles']));
|
||||
req.container.registerSingleton(User(xRoles));
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
app.chain(new PermissionBuilder.wildcard()).get('/', 'Hello, world!');
|
||||
app.chain(new Permission('foo')).get('/one', 'Hello, world!');
|
||||
app.chain(new Permission('two:foo')).get('/two', 'Hello, world!');
|
||||
app.chain(new Permission('two:*')).get('/two-star', 'Hello, world!');
|
||||
app.chain(new Permission('three:foo:bar')).get('/three', 'Hello, world!');
|
||||
app
|
||||
.chain(new Permission('three:*:bar'))
|
||||
.get('/three-star', 'Hello, world!');
|
||||
app.chain([PermissionBuilder.wildcard().toPermission().toMiddleware()]).get(
|
||||
'/', (req, res) => 'Hello, world!');
|
||||
app.chain([Permission('foo').toMiddleware()]).get(
|
||||
'/one', (req, res) => 'Hello, world!');
|
||||
app.chain([Permission('two:foo').toMiddleware()]).get(
|
||||
'/two', (req, res) => 'Hello, world!');
|
||||
app.chain([Permission('two:*').toMiddleware()]).get(
|
||||
'/two-star', (req, res) => 'Hello, world!');
|
||||
app.chain([Permission('three:foo:bar').toMiddleware()]).get(
|
||||
'/three', (req, res) => 'Hello, world!');
|
||||
app.chain([Permission('three:*:bar').toMiddleware()]).get(
|
||||
'/three-star', (req, res) => 'Hello, world!');
|
||||
|
||||
app
|
||||
.chain(new PermissionBuilder('super')
|
||||
.add('specific')
|
||||
.add('permission')
|
||||
.allowAll()
|
||||
.or(new PermissionBuilder('admin')))
|
||||
.get('/or', 'Hello, world!');
|
||||
app.chain([
|
||||
PermissionBuilder('super')
|
||||
.add('specific')
|
||||
.add('permission')
|
||||
.allowAll()
|
||||
.or(PermissionBuilder('admin'))
|
||||
.toPermission()
|
||||
.toMiddleware()
|
||||
]).get('/or', (req, res) => 'Hello, world!');
|
||||
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
|
|
@ -3,15 +3,13 @@ import 'package:logging/logging.dart';
|
|||
|
||||
/// Prints the contents of a [LogRecord] with pretty colors.
|
||||
prettyLog(LogRecord record) async {
|
||||
var pen = new TextPen();
|
||||
var pen = TextPen();
|
||||
chooseLogColor(pen.reset(), record.level);
|
||||
pen(record.toString());
|
||||
|
||||
if (record.error != null)
|
||||
pen(record.error.toString());
|
||||
if (record.stackTrace != null)
|
||||
pen(record.stackTrace.toString());
|
||||
|
||||
|
||||
if (record.error != null) pen(record.error.toString());
|
||||
if (record.stackTrace != null) pen(record.stackTrace.toString());
|
||||
|
||||
pen();
|
||||
}
|
||||
|
||||
|
@ -27,6 +25,5 @@ void chooseLogColor(TextPen pen, Level level) {
|
|||
pen.magenta();
|
||||
else if (level == Level.FINER)
|
||||
pen.blue();
|
||||
else if (level == Level.FINEST)
|
||||
pen.darkBlue();
|
||||
else if (level == Level.FINEST) pen.darkBlue();
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_security/angel_security.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:angel_validate/server.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'pretty_logging.dart';
|
||||
|
||||
final Validator untrustedSchema = new Validator({'html*': isString});
|
||||
final Validator untrustedSchema = Validator({'html*': isString});
|
||||
|
||||
main() async {
|
||||
Angel app;
|
||||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel();
|
||||
app = Angel();
|
||||
app.chain([validate(untrustedSchema), sanitizeHtmlInput()])
|
||||
..post('/untrusted', (RequestContext req, ResponseContext res) async {
|
||||
String untrusted = req.body['html'];
|
||||
String untrusted = req.bodyAsMap['html'];
|
||||
res
|
||||
..contentType = ContentType.HTML
|
||||
..contentType = MediaType('text', 'html')
|
||||
..write('''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -31,9 +31,9 @@ main() async {
|
|||
</html>''');
|
||||
})
|
||||
..post('/attribute', (RequestContext req, ResponseContext res) async {
|
||||
String untrusted = req.body['html'];
|
||||
String untrusted = req.bodyAsMap['html'];
|
||||
res
|
||||
..contentType = ContentType.HTML
|
||||
..contentType = MediaType('text', 'html')
|
||||
..write('''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -46,8 +46,7 @@ main() async {
|
|||
</html>''');
|
||||
});
|
||||
|
||||
app.logger = new Logger.detached('angel_security')
|
||||
..onRecord.listen(prettyLog);
|
||||
app.logger = Logger.detached('angel_security')..onRecord.listen(prettyLog);
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,15 +8,13 @@ main() {
|
|||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel();
|
||||
app = Angel();
|
||||
|
||||
app
|
||||
.chain(throttleRequests(1, new Duration(hours: 1)))
|
||||
.get('/once-per-hour', 'OK');
|
||||
app.chain([throttleRequests(1, Duration(hours: 1))]).get(
|
||||
'/once-per-hour', (req, res) => 'OK');
|
||||
|
||||
app
|
||||
.chain(throttleRequests(3, new Duration(minutes: 1)))
|
||||
.get('/thrice-per-minute', 'OK');
|
||||
app.chain([throttleRequests(3, Duration(minutes: 1))]).get(
|
||||
'/thrice-per-minute', (req, res) => 'OK');
|
||||
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
@ -45,13 +43,11 @@ main() {
|
|||
print(response.body);
|
||||
expect(response, hasBody('"OK"'));
|
||||
|
||||
|
||||
// Second request within the minute is fine
|
||||
response = await client.get('/thrice-per-minute');
|
||||
print(response.body);
|
||||
expect(response, hasBody('"OK"'));
|
||||
|
||||
|
||||
// Third request within the minute is fine
|
||||
response = await client.get('/thrice-per-minute');
|
||||
print(response.body);
|
||||
|
|
|
@ -3,17 +3,17 @@ import 'package:angel_security/angel_security.dart';
|
|||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
verifyProxy(RequestContext req) =>
|
||||
req.injections.containsKey(ForwardedClient) ? 'Yep' : 'Nope';
|
||||
verifyProxy(RequestContext req, ResponseContext res) =>
|
||||
req.container.has<ForwardedClient>() ? 'Yep' : 'Nope';
|
||||
|
||||
main() {
|
||||
Angel app;
|
||||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel()
|
||||
..chain(trustProxy('127.*.*.*')).get('/hello', verifyProxy)
|
||||
..chain(trustProxy('1.2.3.4')).get('/foo', verifyProxy);
|
||||
app = Angel()
|
||||
..chain([trustProxy('127.*.*.*')]).get('/hello', verifyProxy)
|
||||
..chain([trustProxy('1.2.3.4')]).get('/foo', verifyProxy);
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue