Skip to content

Commit

Permalink
policy: add periodic policy checks
Browse files Browse the repository at this point in the history
  • Loading branch information
dufkan committed May 3, 2024
1 parent f3cb771 commit b813d53
Showing 1 changed file with 118 additions and 18 deletions.
136 changes: 118 additions & 18 deletions meesign_core/bin/policy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,48 @@ import 'dart:convert';

import 'package:args/args.dart';
import 'package:meesign_core/meesign_core.dart';
import 'package:meta/meta.dart';

extension Range<T> on Comparable<T> {
bool within(T a, T b) => compareTo(a) >= 0 && compareTo(b) <= 0;
}

@immutable
class Time implements Comparable<Time> {
final int hour;
final int minute;

const Time({required this.hour, required this.minute});

factory Time.now() {
final now = DateTime.now();
return Time(hour: now.hour, minute: now.minute);
}

static Time parse(String string) {
final parts = string.trim().split(':');
if (parts.length != 2) throw FormatException('Invalid time format');

final hour = int.parse(parts[0]);
if (!hour.within(0, 23)) throw FormatException('Hour out of range');
final minute = int.parse(parts[1]);
if (!minute.within(0, 59)) throw FormatException('Minute out of range');

return Time(hour: hour, minute: minute);
}

@override
String toString() {
String pad(num n) => n.toString().padLeft(2, '0');
return '${pad(hour)}:${pad(minute)}';
}

@override
int compareTo(Time other) {
int encode(Time t) => 60 * t.hour + t.minute;
return encode(this) - encode(other);
}
}

Group getGroup<T>(Task<T> task) {
if (task.info is Challenge) {
Expand All @@ -20,6 +62,20 @@ Group getGroup<T>(Task<T> task) {
}
}

String getName<T>(Task<T> task) {
if (task.info is Challenge) {
return (task.info as Challenge).name;
} else if (task.info is Decrypt) {
return (task.info as Decrypt).name;
} else if (task.info is File) {
return (task.info as File).path;
} else if (task.info is Group) {
return (task.info as Group).name;
} else {
throw ArgumentError('Unknown task type');
}
}

extension TaskDecision<T> on TaskRepository<T> {
StreamSubscription<Task<T>> approveAll(Uuid did) {
return observeTasks(did)
Expand All @@ -30,15 +86,29 @@ extension TaskDecision<T> on TaskRepository<T> {
});
}

StreamSubscription<Task<T>> decide(Uuid did,
{required bool Function(Task<T>) agree}) {
return observeTasks(did)
.expand((tasks) => tasks)
.where((task) => !task.approved)
.listen((task) async {
var g = getGroup(task);
print("Deciding: ${g.name}, ${g.note}");
await approveTask(did, task.id, agree: agree(task));
void decide(Uuid did, Map<String, dynamic> basePolicy) {
observeTasks(did).first.then((tasks) async {
for (var task in tasks) {
if (task.approved) continue;

final group = getGroup(task);
late final Map<String, dynamic> extPolicy;
try {
extPolicy = jsonDecode(group.note ?? '{}');
} on Exception catch (e) {
extPolicy = {};
}

final result = evalPolicy(basePolicy, extPolicy, task);
if (result != null) {
if (result) {
print('Approved task: ${getName(task)} $extPolicy');
} else {
print('Declined task: ${getName(task)} $extPolicy');
}
await approveTask(did, task.id, agree: result);
}
}
});
}
}
Expand All @@ -59,9 +129,34 @@ void printUsage(ArgParser parser, IOSink sink) {
sink.writeln(parser.usage);
}

bool Function(Task<T>) constructPolicy<T>(Map<String, dynamic> policy) {
final approve = policy["deny"] != true;
return (Task _) => approve;
bool? evalPolicy<T>(Map<String, dynamic> basePolicy, Map<String, dynamic> extPolicy, Task<T> task) {
var policy = basePolicy;
if (policy["overridable"] ?? true) {
policy = {...basePolicy, ...extPolicy};
}
var result = true;

if (policy["fail"] ?? false) {
result = false;
}

if ((policy["from"] ?? '').isNotEmpty && (policy["to"] ?? '').isNotEmpty) {
try {
final Time from = Time.parse(policy['from']);
final Time to = Time.parse(policy['to']);
result = result && Time.now().within(from, to);
} on Exception catch (e) {
print("Error parsing time: $e");
}
}

if (result) {
return true;
}
if (policy["deny"] ?? false) {
return false;
}
return null;
}

void main(List<String> args) async {
Expand Down Expand Up @@ -111,8 +206,6 @@ void main(List<String> args) async {
}
}

final policy = constructPolicy(policyData);

final appDir = Directory('app/');

final database = Database(appDir);
Expand Down Expand Up @@ -144,17 +237,24 @@ void main(List<String> args) async {
device = await deviceRepository.getDevice(user.did);
}
print('Logged in as ${device.name}#${device.id.encode().substring(0, 4)}');
print('Enforcing policy: $policyData');
print('Base policy: $policyData');

await groupRepository.subscribe(device.id);
await fileRepository.subscribe(device.id);
await challengeRepository.subscribe(device.id);
await decryptRepository.subscribe(device.id);

groupRepository.approveAll(device.id);
fileRepository.decide(device.id, agree: policy);
challengeRepository.decide(device.id, agree: policy);
decryptRepository.decide(device.id, agree: policy);

Timer.periodic(Duration(seconds: 5), (_) {
fileRepository.decide(device.id, policyData);
});
Timer.periodic(Duration(seconds: 5), (_) {
challengeRepository.decide(device.id, policyData);
});
Timer.periodic(Duration(seconds: 5), (_) {
decryptRepository.decide(device.id, policyData);
});

ProcessSignal.sigint.watch().listen((signal) {
database.close();
Expand Down

0 comments on commit b813d53

Please sign in to comment.