Permissions

This commit is contained in:
thosakwe 2017-01-20 22:09:37 -05:00
parent bc991412fc
commit 3774101713
5 changed files with 263 additions and 3 deletions

View file

@ -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.

View file

@ -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
View 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);
}

View file

@ -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
View 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));
});
}