Skip to content

Commit f2e3054

Browse files
Copilotnomeguy
andcommitted
Add ABAC JSON string support with comprehensive tests
Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com>
1 parent 2b9792c commit f2e3054

3 files changed

Lines changed: 356 additions & 12 deletions

File tree

example/abac_json_example.dart

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2018-2021 The Casbin Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import 'package:casbin/casbin.dart';
16+
import 'dart:convert';
17+
18+
/// This example demonstrates ABAC (Attribute-Based Access Control)
19+
/// using JSON strings as subjects.
20+
///
21+
/// This is particularly useful for Flutter/Dart applications where
22+
/// you want to pass user attributes as JSON without creating
23+
/// custom classes that implement AbacClass.
24+
void main() {
25+
print('=== ABAC with JSON Strings Example ===\n');
26+
27+
// Define the model with ABAC support
28+
final model = Model()..loadModelFromText('''
29+
[request_definition]
30+
r = sub, obj, act
31+
32+
[policy_definition]
33+
p = sub, obj, act, condition
34+
35+
[policy_effect]
36+
e = some(where (p.eft == allow))
37+
38+
[matchers]
39+
m = eval(p.condition) && r.obj == p.obj && r.act == p.act
40+
''');
41+
42+
// Create an enforcer
43+
final enforcer = Enforcer.initWithModelAndAdapter(model);
44+
45+
// Add policies with attribute-based conditions
46+
print('Adding policies...');
47+
enforcer.addPolicy([
48+
'{"age": 18}',
49+
'/data1',
50+
'read',
51+
'r.sub.age >= 18 && r.sub.age < 60'
52+
]);
53+
54+
enforcer.addPolicy([
55+
'{"role": "admin"}',
56+
'/admin',
57+
'write',
58+
'r.sub.role == "admin" && r.sub.age >= 21'
59+
]);
60+
61+
print('Policies added.\n');
62+
63+
// Test Case 1: Valid age for /data1
64+
print('--- Test Case 1: User with age 25 accessing /data1 ---');
65+
String user1 = jsonEncode({"age": 25});
66+
bool result1 = enforcer.enforce([user1, '/data1', 'read']);
67+
print('User: $user1');
68+
print('Resource: /data1, Action: read');
69+
print('Result: ${result1 ? "✅ Access Granted" : "❌ Access Denied"}\n');
70+
71+
// Test Case 2: Age below minimum
72+
print('--- Test Case 2: User with age 16 accessing /data1 ---');
73+
String user2 = jsonEncode({"age": 16});
74+
bool result2 = enforcer.enforce([user2, '/data1', 'read']);
75+
print('User: $user2');
76+
print('Resource: /data1, Action: read');
77+
print('Result: ${result2 ? "✅ Access Granted" : "❌ Access Denied"}\n');
78+
79+
// Test Case 3: Age above maximum
80+
print('--- Test Case 3: User with age 65 accessing /data1 ---');
81+
String user3 = jsonEncode({"age": 65});
82+
bool result3 = enforcer.enforce([user3, '/data1', 'read']);
83+
print('User: $user3');
84+
print('Resource: /data1, Action: read');
85+
print('Result: ${result3 ? "✅ Access Granted" : "❌ Access Denied"}\n');
86+
87+
// Test Case 4: Admin with sufficient age
88+
print('--- Test Case 4: Admin with age 25 accessing /admin ---');
89+
String admin1 = jsonEncode({"role": "admin", "age": 25});
90+
bool result4 = enforcer.enforce([admin1, '/admin', 'write']);
91+
print('User: $admin1');
92+
print('Resource: /admin, Action: write');
93+
print('Result: ${result4 ? "✅ Access Granted" : "❌ Access Denied"}\n');
94+
95+
// Test Case 5: Admin with insufficient age
96+
print('--- Test Case 5: Admin with age 20 accessing /admin ---');
97+
String admin2 = jsonEncode({"role": "admin", "age": 20});
98+
bool result5 = enforcer.enforce([admin2, '/admin', 'write']);
99+
print('User: $admin2');
100+
print('Resource: /admin, Action: write');
101+
print('Result: ${result5 ? "✅ Access Granted" : "❌ Access Denied"}\n');
102+
103+
// Test Case 6: Non-admin user
104+
print('--- Test Case 6: Regular user accessing /admin ---');
105+
String user4 = jsonEncode({"role": "user", "age": 25});
106+
bool result6 = enforcer.enforce([user4, '/admin', 'write']);
107+
print('User: $user4');
108+
print('Resource: /admin, Action: write');
109+
print('Result: ${result6 ? "✅ Access Granted" : "❌ Access Denied"}\n');
110+
111+
print('=== Example Complete ===');
112+
}

lib/src/utils/utils.dart

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import 'dart:convert';
16+
1517
import 'package:collection/collection.dart';
1618
import 'package:expressions/expressions.dart';
1719

20+
import '../abac/abac_class.dart';
1821
import '../model/assertion.dart';
1922

2023
class CasbinEvaluator extends ExpressionEvaluator {
@@ -23,7 +26,35 @@ class CasbinEvaluator extends ExpressionEvaluator {
2326
@override
2427
dynamic evalMemberExpression(
2528
MemberExpression expression, Map<String, dynamic> context) {
26-
var object = eval(expression.object, context).toMap();
29+
var objectValue = eval(expression.object, context);
30+
Map<String, dynamic> object;
31+
32+
// Handle different types of objects
33+
if (objectValue is String) {
34+
// Try to parse as JSON
35+
try {
36+
var parsed = jsonDecode(objectValue);
37+
if (parsed is Map<String, dynamic>) {
38+
object = parsed;
39+
} else {
40+
throw Exception('JSON string must represent an object/map');
41+
}
42+
} catch (e) {
43+
throw Exception('Failed to parse JSON string: $e');
44+
}
45+
} else if (objectValue is AbacClass) {
46+
object = objectValue.toMap();
47+
} else if (objectValue is Map<String, dynamic>) {
48+
object = objectValue;
49+
} else {
50+
// Fall back to trying toMap() for backward compatibility
51+
try {
52+
object = objectValue.toMap();
53+
} catch (e) {
54+
throw Exception('Object must be a JSON string, Map, or implement AbacClass: ${objectValue.runtimeType}');
55+
}
56+
}
57+
2758
return object[expression.property.name];
2859
}
2960
}

0 commit comments

Comments
 (0)