Permissions
This commit is contained in:
parent
bc991412fc
commit
3774101713
5 changed files with 263 additions and 3 deletions
|
@ -1,5 +1,5 @@
|
||||||
# security
|
# 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)
|
[![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
|
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)
|
* [Throttling Requests](#throttling-requests)
|
||||||
* [Helmet Port](#helmet)
|
* [Helmet Port](#helmet)
|
||||||
* [Service Hooks](#service-hooks)
|
* [Service Hooks](#service-hooks)
|
||||||
|
* [Permissions](#permissions)
|
||||||
|
|
||||||
## Sanitizing HTML
|
## Sanitizing HTML
|
||||||
|
|
||||||
|
@ -84,3 +85,6 @@ Also included are a set of service hooks, [ported from FeathersJS](https://githu
|
||||||
```dart
|
```dart
|
||||||
import 'package:angel_security/hooks.dart';
|
import 'package:angel_security/hooks.dart';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Permissions
|
||||||
|
See the tests.
|
|
@ -3,6 +3,7 @@ library angel_security;
|
||||||
|
|
||||||
export 'src/ban.dart';
|
export 'src/ban.dart';
|
||||||
export 'src/csrf.dart';
|
export 'src/csrf.dart';
|
||||||
|
export 'src/permissions.dart';
|
||||||
export 'src/sanitize.dart';
|
export 'src/sanitize.dart';
|
||||||
export 'src/throttle.dart';
|
export 'src/throttle.dart';
|
||||||
export 'src/trust_proxy.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
|
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.
|
description: Angel middleware designed to enhance application security by patching common Web security holes.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
environment:
|
environment:
|
||||||
|
@ -8,6 +8,7 @@ homepage: https://github.com/angel-dart/security
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_framework: ^1.0.0-dev
|
angel_framework: ^1.0.0-dev
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
angel_auth: ^1.0.0-dev
|
||||||
angel_diagnostics: ^1.0.0-dev
|
angel_diagnostics: ^1.0.0-dev
|
||||||
angel_validate: ^1.0.0-beta
|
angel_validate: ^1.0.0-beta
|
||||||
angel_test: ^1.0.0-dev
|
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