Permissions
This commit is contained in:
parent
bc991412fc
commit
3774101713
5 changed files with 263 additions and 3 deletions
|
@ -1,5 +1,5 @@
|
|||
# security
|
||||
[![version 0.0.0-alpha+2](https://img.shields.io/badge/pub-v0.0.0--alpha+2-red.svg)](https://pub.dartlang.org/packages/angel_security)
|
||||
[![version 0.0.0-alpha+3](https://img.shields.io/badge/pub-v0.0.0--alpha+3-red.svg)](https://pub.dartlang.org/packages/angel_security)
|
||||
[![build status](https://travis-ci.org/angel-dart/security.svg)](https://travis-ci.org/angel-dart/security)
|
||||
|
||||
Angel middleware designed to enhance application security by patching common Web security
|
||||
|
@ -15,6 +15,7 @@ Currently unfinished, with incomplete code coverage - **USE AT YOUR OWN RISK!!!*
|
|||
* [Throttling Requests](#throttling-requests)
|
||||
* [Helmet Port](#helmet)
|
||||
* [Service Hooks](#service-hooks)
|
||||
* [Permissions](#permissions)
|
||||
|
||||
## Sanitizing HTML
|
||||
|
||||
|
@ -84,3 +85,6 @@ Also included are a set of service hooks, [ported from FeathersJS](https://githu
|
|||
```dart
|
||||
import 'package:angel_security/hooks.dart';
|
||||
```
|
||||
|
||||
# Permissions
|
||||
See the tests.
|
|
@ -3,6 +3,7 @@ library angel_security;
|
|||
|
||||
export 'src/ban.dart';
|
||||
export 'src/csrf.dart';
|
||||
export 'src/permissions.dart';
|
||||
export 'src/sanitize.dart';
|
||||
export 'src/throttle.dart';
|
||||
export 'src/trust_proxy.dart';
|
||||
|
|
109
lib/src/permissions.dart
Normal file
109
lib/src/permissions.dart
Normal file
|
@ -0,0 +1,109 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
|
||||
/// Easy mechanism to restrict access to services or routes.
|
||||
class Permission {
|
||||
final String minimum;
|
||||
|
||||
Permission(this.minimum);
|
||||
|
||||
call(RequestContext req, ResponseContext res) {
|
||||
return toMiddleware()(req, res);
|
||||
}
|
||||
|
||||
HookedServiceEventListener toHook(
|
||||
{String message, String userKey, getRoles(user)}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
var user = e.request.grab(userKey ?? 'user');
|
||||
|
||||
if (user == null)
|
||||
throw new AngelHttpException.forbidden(
|
||||
message: message ??
|
||||
'You have insufficient permissions to perform this action.');
|
||||
|
||||
var roleFinder = getRoles ?? (user) async => user.roles ?? [];
|
||||
List<String> roles = (await roleFinder(user)).toList();
|
||||
|
||||
if (!roles.any(verify))
|
||||
throw new AngelHttpException.forbidden(
|
||||
message: message ??
|
||||
'You have insufficient permissions to perform this action.');
|
||||
};
|
||||
}
|
||||
|
||||
RequestMiddleware toMiddleware(
|
||||
{String message, String userKey, getRoles(user)}) {
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
var user = req.grab(userKey ?? 'user');
|
||||
|
||||
if (user == null)
|
||||
throw new AngelHttpException.forbidden(
|
||||
message: message ??
|
||||
'You have insufficient permissions to perform this action.');
|
||||
|
||||
var roleFinder = getRoles ?? (user) async => user.roles ?? [];
|
||||
List<String> roles = (await roleFinder(user)).toList();
|
||||
|
||||
if (!roles.any(verify))
|
||||
throw new AngelHttpException.forbidden(
|
||||
message: message ??
|
||||
'You have insufficient permissions to perform this action.');
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
bool verify(String given) {
|
||||
bool verifyOne(String minimum) {
|
||||
if (minimum == '*') return true;
|
||||
|
||||
var minSplit = minimum.split(':');
|
||||
var split = given.split(':');
|
||||
|
||||
for (int i = 0; i < minSplit.length; i++) {
|
||||
if (i >= split.length) return false;
|
||||
var min = minSplit[i], giv = split[i];
|
||||
|
||||
if (min == '*' || min == giv) {
|
||||
if (i >= minSplit.length - 1)
|
||||
return true;
|
||||
else
|
||||
continue;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var minima = minimum
|
||||
.split('|')
|
||||
.map((str) => str.trim())
|
||||
.where((str) => str.isNotEmpty);
|
||||
return minima.any(verifyOne);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'Permission: $minimum';
|
||||
}
|
||||
|
||||
class PermissionBuilder {
|
||||
String _min;
|
||||
|
||||
PermissionBuilder(this._min);
|
||||
|
||||
factory PermissionBuilder.wildcard() => new PermissionBuilder('*');
|
||||
|
||||
call(RequestContext req, ResponseContext res) => toPermission()(req, res);
|
||||
|
||||
PermissionBuilder add(String constraint) =>
|
||||
new PermissionBuilder('$_min:$constraint');
|
||||
|
||||
PermissionBuilder allowAll() => add('*');
|
||||
|
||||
PermissionBuilder clone() => new PermissionBuilder(_min);
|
||||
|
||||
PermissionBuilder or(PermissionBuilder other) =>
|
||||
new PermissionBuilder('$_min | ${other._min}');
|
||||
|
||||
Permission toPermission() => new Permission(_min);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
name: angel_security
|
||||
version: 0.0.0-alpha+2
|
||||
version: 0.0.0-alpha+3
|
||||
description: Angel middleware designed to enhance application security by patching common Web security holes.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
environment:
|
||||
|
@ -8,6 +8,7 @@ homepage: https://github.com/angel-dart/security
|
|||
dependencies:
|
||||
angel_framework: ^1.0.0-dev
|
||||
dev_dependencies:
|
||||
angel_auth: ^1.0.0-dev
|
||||
angel_diagnostics: ^1.0.0-dev
|
||||
angel_validate: ^1.0.0-beta
|
||||
angel_test: ^1.0.0-dev
|
||||
|
|
145
test/permission_test.dart
Normal file
145
test/permission_test.dart
Normal file
|
@ -0,0 +1,145 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_security/angel_security.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class User {
|
||||
final List<String> roles;
|
||||
|
||||
User(this.roles);
|
||||
}
|
||||
|
||||
main() {
|
||||
Angel app;
|
||||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel();
|
||||
|
||||
app.before.add((RequestContext 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');
|
||||
|
||||
if (xRoles?.isNotEmpty == true) {
|
||||
req.inject('user', new User(req.headers['X-Roles']));
|
||||
}
|
||||
|
||||
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(new PermissionBuilder('super')
|
||||
.add('specific')
|
||||
.add('permission')
|
||||
.allowAll()
|
||||
.or(new PermissionBuilder('admin')))
|
||||
.get('/or', 'Hello, world!');
|
||||
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
||||
tearDown(() => client.close());
|
||||
|
||||
test('open permission', () async {
|
||||
var response = await client.get('/', headers: {'X-Roles': 'foo'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
});
|
||||
|
||||
group('restrict', () {
|
||||
test('one', () async {
|
||||
var response = await client.get('/one', headers: {'X-Roles': 'foo'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
|
||||
response = await client.get('/one', headers: {'X-Roles': 'bar'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(403));
|
||||
});
|
||||
|
||||
test('two', () async {
|
||||
var response = await client.get('/two', headers: {'X-Roles': 'two:foo'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
|
||||
response = await client.get('/two', headers: {'X-Roles': 'two:bar'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(403));
|
||||
});
|
||||
|
||||
test('two with star', () async {
|
||||
var response =
|
||||
await client.get('/two-star', headers: {'X-Roles': 'two:foo'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
|
||||
response =
|
||||
await client.get('/two-star', headers: {'X-Roles': 'three:foo'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(403));
|
||||
});
|
||||
|
||||
test('three', () async {
|
||||
var response =
|
||||
await client.get('/three', headers: {'X-Roles': 'three:foo:bar'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
|
||||
response =
|
||||
await client.get('/three', headers: {'X-Roles': 'three:foo:baz'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(403));
|
||||
|
||||
response =
|
||||
await client.get('/three', headers: {'X-Roles': 'three:foz:bar'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(403));
|
||||
});
|
||||
|
||||
test('three with star', () async {
|
||||
var response = await client
|
||||
.get('/three-star', headers: {'X-Roles': 'three:foo:bar'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
|
||||
response = await client
|
||||
.get('/three-star', headers: {'X-Roles': 'three:foz:bar'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
|
||||
response = await client
|
||||
.get('/three-star', headers: {'X-Roles': 'three:foo:baz'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(403));
|
||||
});
|
||||
});
|
||||
|
||||
test('or', () async {
|
||||
var response = await client.get('/or', headers: {'X-Roles': 'admin'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(200));
|
||||
expect(response, isJson('Hello, world!'));
|
||||
|
||||
response = await client
|
||||
.get('/or', headers: {'X-Roles': 'not:specific:enough:i:guess'});
|
||||
print('Response: ${response.body}');
|
||||
expect(response, hasStatus(403));
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue