From 8d263d55e89b86e27c1c09ca3edd755a2772a913 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:34:49 +0000 Subject: [PATCH 1/7] Initial plan From 2b9792c08bb55041d9498603c21c61b669b5add0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:38:57 +0000 Subject: [PATCH 2/7] Initial plan for ABAC JSON support Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com> --- test/json_abac_test.dart | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/json_abac_test.dart diff --git a/test/json_abac_test.dart b/test/json_abac_test.dart new file mode 100644 index 0000000..73ce94e --- /dev/null +++ b/test/json_abac_test.dart @@ -0,0 +1,34 @@ +import 'package:casbin/casbin.dart'; +import 'package:test/test.dart'; +import 'dart:convert'; + +void main() { + test('ABAC with JSON string should work', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add the policy from the issue + enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); + + // Test with JSON string (as the user tries to do) + String subJson = jsonEncode({"age": 25}); + print("Testing with subject: $subJson"); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + print("Result: $result"); + + expect(result, isTrue); + }); +} From f2e30549a77a4fc9c97b5ba73861a158ba4d9837 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:41:27 +0000 Subject: [PATCH 3/7] Add ABAC JSON string support with comprehensive tests Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com> --- example/abac_json_example.dart | 112 +++++++++++++++++ lib/src/utils/utils.dart | 33 ++++- test/json_abac_test.dart | 223 +++++++++++++++++++++++++++++++-- 3 files changed, 356 insertions(+), 12 deletions(-) create mode 100644 example/abac_json_example.dart diff --git a/example/abac_json_example.dart b/example/abac_json_example.dart new file mode 100644 index 0000000..effb56a --- /dev/null +++ b/example/abac_json_example.dart @@ -0,0 +1,112 @@ +// Copyright 2018-2021 The Casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:casbin/casbin.dart'; +import 'dart:convert'; + +/// This example demonstrates ABAC (Attribute-Based Access Control) +/// using JSON strings as subjects. +/// +/// This is particularly useful for Flutter/Dart applications where +/// you want to pass user attributes as JSON without creating +/// custom classes that implement AbacClass. +void main() { + print('=== ABAC with JSON Strings Example ===\n'); + + // Define the model with ABAC support + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + // Create an enforcer + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add policies with attribute-based conditions + print('Adding policies...'); + enforcer.addPolicy([ + '{"age": 18}', + '/data1', + 'read', + 'r.sub.age >= 18 && r.sub.age < 60' + ]); + + enforcer.addPolicy([ + '{"role": "admin"}', + '/admin', + 'write', + 'r.sub.role == "admin" && r.sub.age >= 21' + ]); + + print('Policies added.\n'); + + // Test Case 1: Valid age for /data1 + print('--- Test Case 1: User with age 25 accessing /data1 ---'); + String user1 = jsonEncode({"age": 25}); + bool result1 = enforcer.enforce([user1, '/data1', 'read']); + print('User: $user1'); + print('Resource: /data1, Action: read'); + print('Result: ${result1 ? "✅ Access Granted" : "❌ Access Denied"}\n'); + + // Test Case 2: Age below minimum + print('--- Test Case 2: User with age 16 accessing /data1 ---'); + String user2 = jsonEncode({"age": 16}); + bool result2 = enforcer.enforce([user2, '/data1', 'read']); + print('User: $user2'); + print('Resource: /data1, Action: read'); + print('Result: ${result2 ? "✅ Access Granted" : "❌ Access Denied"}\n'); + + // Test Case 3: Age above maximum + print('--- Test Case 3: User with age 65 accessing /data1 ---'); + String user3 = jsonEncode({"age": 65}); + bool result3 = enforcer.enforce([user3, '/data1', 'read']); + print('User: $user3'); + print('Resource: /data1, Action: read'); + print('Result: ${result3 ? "✅ Access Granted" : "❌ Access Denied"}\n'); + + // Test Case 4: Admin with sufficient age + print('--- Test Case 4: Admin with age 25 accessing /admin ---'); + String admin1 = jsonEncode({"role": "admin", "age": 25}); + bool result4 = enforcer.enforce([admin1, '/admin', 'write']); + print('User: $admin1'); + print('Resource: /admin, Action: write'); + print('Result: ${result4 ? "✅ Access Granted" : "❌ Access Denied"}\n'); + + // Test Case 5: Admin with insufficient age + print('--- Test Case 5: Admin with age 20 accessing /admin ---'); + String admin2 = jsonEncode({"role": "admin", "age": 20}); + bool result5 = enforcer.enforce([admin2, '/admin', 'write']); + print('User: $admin2'); + print('Resource: /admin, Action: write'); + print('Result: ${result5 ? "✅ Access Granted" : "❌ Access Denied"}\n'); + + // Test Case 6: Non-admin user + print('--- Test Case 6: Regular user accessing /admin ---'); + String user4 = jsonEncode({"role": "user", "age": 25}); + bool result6 = enforcer.enforce([user4, '/admin', 'write']); + print('User: $user4'); + print('Resource: /admin, Action: write'); + print('Result: ${result6 ? "✅ Access Granted" : "❌ Access Denied"}\n'); + + print('=== Example Complete ==='); +} diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 0c9b9c4..d1d599b 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:convert'; + import 'package:collection/collection.dart'; import 'package:expressions/expressions.dart'; +import '../abac/abac_class.dart'; import '../model/assertion.dart'; class CasbinEvaluator extends ExpressionEvaluator { @@ -23,7 +26,35 @@ class CasbinEvaluator extends ExpressionEvaluator { @override dynamic evalMemberExpression( MemberExpression expression, Map context) { - var object = eval(expression.object, context).toMap(); + var objectValue = eval(expression.object, context); + Map object; + + // Handle different types of objects + if (objectValue is String) { + // Try to parse as JSON + try { + var parsed = jsonDecode(objectValue); + if (parsed is Map) { + object = parsed; + } else { + throw Exception('JSON string must represent an object/map'); + } + } catch (e) { + throw Exception('Failed to parse JSON string: $e'); + } + } else if (objectValue is AbacClass) { + object = objectValue.toMap(); + } else if (objectValue is Map) { + object = objectValue; + } else { + // Fall back to trying toMap() for backward compatibility + try { + object = objectValue.toMap(); + } catch (e) { + throw Exception('Object must be a JSON string, Map, or implement AbacClass: ${objectValue.runtimeType}'); + } + } + return object[expression.property.name]; } } diff --git a/test/json_abac_test.dart b/test/json_abac_test.dart index 73ce94e..5a4273d 100644 --- a/test/json_abac_test.dart +++ b/test/json_abac_test.dart @@ -3,8 +3,9 @@ import 'package:test/test.dart'; import 'dart:convert'; void main() { - test('ABAC with JSON string should work', () { - final model = Model()..loadModelFromText(''' + group('ABAC with JSON strings', () { + test('should allow access when age is in valid range', () { + final model = Model()..loadModelFromText(''' [request_definition] r = sub, obj, act @@ -18,17 +19,217 @@ e = some(where (p.eft == allow)) m = eval(p.condition) && r.obj == p.obj && r.act == p.act '''); - final enforcer = Enforcer.initWithModelAndAdapter(model); + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add the policy from the issue + enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); + + // Test with JSON string - age 25 should be allowed + String subJson = jsonEncode({"age": 25}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isTrue); + }); - // Add the policy from the issue - enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); + test('should deny access when age is below minimum', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add the policy from the issue + enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); + + // Test with JSON string - age 16 should be denied + String subJson = jsonEncode({"age": 16}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isFalse); + }); + + test('should deny access when age is above maximum', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add the policy from the issue + enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); + + // Test with JSON string - age 65 should be denied + String subJson = jsonEncode({"age": 65}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isFalse); + }); - // Test with JSON string (as the user tries to do) - String subJson = jsonEncode({"age": 25}); - print("Testing with subject: $subJson"); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - print("Result: $result"); + test('should work with multiple attributes in JSON', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add policy with multiple attribute checks + enforcer.addPolicy(['{"age": 18, "role": "user"}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.role == "admin"']); + + // Test with JSON string - should be allowed + String subJson = jsonEncode({"age": 25, "role": "admin"}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isTrue); + }); - expect(result, isTrue); + test('should deny with wrong role attribute', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add policy with multiple attribute checks + enforcer.addPolicy(['{"age": 18, "role": "user"}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.role == "admin"']); + + // Test with JSON string - should be denied due to role mismatch + String subJson = jsonEncode({"age": 25, "role": "user"}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isFalse); + }); + + test('should work with boundary value at minimum', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); + + // Test with age exactly at minimum boundary + String subJson = jsonEncode({"age": 18}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isTrue); + }); + + test('should deny with boundary value at maximum', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); + + // Test with age exactly at maximum boundary (should be denied because < 60) + String subJson = jsonEncode({"age": 60}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isFalse); + }); }); + + group('ABAC with AbacClass (existing functionality)', () { + test('should still work with AbacClass objects', () { + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub_rule, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + enforcer.addPolicy(['r.sub.Age > 18', '/data1', 'read']); + + // Test with AbacClass instance + final subject = TestSubject(25, 'Alice'); + bool result = enforcer.enforce([subject, '/data1', 'read']); + + expect(result, isTrue); + }); + }); +} + +// Test class that implements AbacClass +class TestSubject implements AbacClass { + final int Age; + final String Name; + + TestSubject(this.Age, this.Name); + + @override + Map toMap() { + return {'Age': Age, 'Name': Name}; + } } From 6490000265c0273bd84c756741cf6001c02692f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:44:17 +0000 Subject: [PATCH 4/7] Add Flutter example and update README with JSON ABAC documentation Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com> --- README.md | 61 +++++++ example/flutter_abac_example.dart | 266 ++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 example/flutter_abac_example.dart diff --git a/README.md b/README.md index ebad2f2..07f2363 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,67 @@ https://casbin.org/docs/tutorials See [Policy management APIs](#policy-management) for more usage. +## ABAC with JSON strings + +Dart-Casbin supports using JSON strings as subjects for Attribute-Based Access Control (ABAC), making it easy to implement ABAC in Flutter and Dart applications without creating custom classes. + +```dart +import 'package:casbin/casbin.dart'; +import 'dart:convert'; + +// Define an ABAC model +final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + +// Create enforcer +final enforcer = Enforcer.initWithModelAndAdapter(model); + +// Add policy with attribute-based condition +enforcer.addPolicy([ + '{"age": 18}', + '/data1', + 'read', + 'r.sub.age >= 18 && r.sub.age < 60' +]); + +// Check access using JSON string +String userJson = jsonEncode({"age": 25}); +bool allowed = enforcer.enforce([userJson, '/data1', 'read']); +// Returns: true (access granted) +``` + +You can also use multiple attributes: + +```dart +// Add policy with multiple attributes +enforcer.addPolicy([ + '{"role": "admin"}', + '/admin', + 'write', + 'r.sub.role == "admin" && r.sub.age >= 21' +]); + +// Check access with multiple user attributes +String adminJson = jsonEncode({"role": "admin", "age": 25}); +bool allowed = enforcer.enforce([adminJson, '/admin', 'write']); +// Returns: true +``` + +For complete examples, see: +- [example/abac_json_example.dart](example/abac_json_example.dart) - Basic ABAC with JSON +- [example/flutter_abac_example.dart](example/flutter_abac_example.dart) - Flutter-specific examples + ## Policy management Casbin provides two sets of APIs to manage permissions: diff --git a/example/flutter_abac_example.dart b/example/flutter_abac_example.dart new file mode 100644 index 0000000..8f3b550 --- /dev/null +++ b/example/flutter_abac_example.dart @@ -0,0 +1,266 @@ +// Copyright 2018-2021 The Casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// This example demonstrates how to use ABAC with JSON strings in Flutter. +/// +/// This approach allows you to pass user attributes directly as JSON without +/// creating custom classes, making it ideal for Flutter applications that +/// receive user data from APIs or other sources. +/// +/// To use this in a Flutter app: +/// 1. Add casbin to your pubspec.yaml: `casbin: ^0.1.0` +/// 2. Import this code into your Flutter widget +/// 3. Call the checkAccess() method when you need to verify permissions +/// +/// Example Flutter widget code: +/// ```dart +/// import 'package:flutter/material.dart'; +/// import 'package:casbin/casbin.dart'; +/// import 'dart:convert'; +/// +/// class ABACScreen extends StatefulWidget { +/// @override +/// _ABACScreenState createState() => _ABACScreenState(); +/// } +/// +/// class _ABACScreenState extends State { +/// final TextEditingController ageController = TextEditingController(); +/// String resultMessage = ""; +/// +/// Future checkAccess() async { +/// int? age = int.tryParse(ageController.text); +/// if (age == null) { +/// setState(() { +/// resultMessage = "❌ Please enter a valid age."; +/// }); +/// return; +/// } +/// +/// // Create model +/// final model = Model()..loadModelFromText(getModel()); +/// final enforcer = Enforcer.initWithModelAndAdapter(model); +/// +/// // Add policy +/// enforcer.addPolicy([ +/// '{"age": 18}', +/// '/data1', +/// 'read', +/// 'r.sub.age >= 18 && r.sub.age < 60' +/// ]); +/// +/// try { +/// // Create JSON string with user attributes +/// String subJson = jsonEncode({"age": age}); +/// +/// // Enforce the policy +/// bool result = enforcer.enforce([subJson, '/data1', 'read']); +/// +/// setState(() { +/// resultMessage = result ? "✅ Access Granted" : "❌ Access Denied"; +/// }); +/// } catch (e) { +/// debugPrint("⚠️ Error: $e"); +/// setState(() { +/// resultMessage = "⚠️ Error: ${e.toString()}"; +/// }); +/// } +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar(title: Text("Casbin ABAC Demo")), +/// body: Padding( +/// padding: EdgeInsets.all(20), +/// child: Column( +/// mainAxisAlignment: MainAxisAlignment.center, +/// children: [ +/// TextField( +/// controller: ageController, +/// keyboardType: TextInputType.number, +/// decoration: InputDecoration( +/// labelText: "Age", +/// border: OutlineInputBorder() +/// ), +/// ), +/// SizedBox(height: 20), +/// ElevatedButton( +/// onPressed: checkAccess, +/// child: Text("Check Access"), +/// ), +/// SizedBox(height: 20), +/// SelectableText( +/// resultMessage, +/// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold) +/// ), +/// ], +/// ), +/// ), +/// ); +/// } +/// } +/// +/// String getModel() { +/// return ''' +/// [request_definition] +/// r = sub, obj, act +/// +/// [policy_definition] +/// p = sub, obj, act, condition +/// +/// [policy_effect] +/// e = some(where (p.eft == allow)) +/// +/// [matchers] +/// m = eval(p.condition) && r.obj == p.obj && r.act == p.act +/// '''; +/// } +/// ``` +/// +/// Key points: +/// - Use jsonEncode() to convert Dart maps to JSON strings +/// - Pass the JSON string directly to enforcer.enforce() +/// - The JSON attributes (like "age") can be accessed in policy conditions using dot notation (r.sub.age) +/// - No need to create custom classes that implement AbacClass +/// +library abac_flutter_example; + +import 'package:casbin/casbin.dart'; +import 'dart:convert'; + +/// Simulates a Flutter app's access control check +void simulateFlutterAccessCheck(int userAge, String resource, String action) { + print('\n--- Simulating Flutter Access Check ---'); + print('User Age: $userAge'); + print('Resource: $resource'); + print('Action: $action'); + + // Create the model + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + // Create enforcer + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add policy (this would typically come from a database or config file) + enforcer.addPolicy([ + '{"age": 18}', + '/data1', + 'read', + 'r.sub.age >= 18 && r.sub.age < 60' + ]); + + // Create JSON string from user attributes + // In a real Flutter app, this data might come from an API response + String subJson = jsonEncode({"age": userAge}); + + // Check access + try { + bool hasAccess = enforcer.enforce([subJson, resource, action]); + print('Result: ${hasAccess ? "✅ Access Granted" : "❌ Access Denied"}'); + } catch (e) { + print('⚠️ Error: $e'); + } +} + +/// Example with multiple user attributes +void simulateComplexFlutterAccessCheck( + String userName, + int userAge, + String userRole, + String resource, + String action, +) { + print('\n--- Simulating Complex Flutter Access Check ---'); + print('User: $userName'); + print('Age: $userAge'); + print('Role: $userRole'); + print('Resource: $resource'); + print('Action: $action'); + + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Add policies with multiple attribute conditions + enforcer.addPolicy([ + '{"name": "user", "age": 18, "role": "user"}', + '/admin', + 'write', + 'r.sub.role == "admin" && r.sub.age >= 21' + ]); + + enforcer.addPolicy([ + '{"name": "user", "age": 18, "role": "user"}', + '/data', + 'read', + 'r.sub.age >= 18' + ]); + + // In a Flutter app, this data typically comes from user authentication + String subJson = jsonEncode({ + "name": userName, + "age": userAge, + "role": userRole, + }); + + try { + bool hasAccess = enforcer.enforce([subJson, resource, action]); + print('Result: ${hasAccess ? "✅ Access Granted" : "❌ Access Denied"}'); + } catch (e) { + print('⚠️ Error: $e'); + } +} + +void main() { + print('=== Flutter ABAC Examples with JSON Strings ==='); + + // Simple examples + simulateFlutterAccessCheck(25, '/data1', 'read'); + simulateFlutterAccessCheck(16, '/data1', 'read'); + simulateFlutterAccessCheck(65, '/data1', 'read'); + + // Complex examples with multiple attributes + simulateComplexFlutterAccessCheck('Alice', 25, 'admin', '/admin', 'write'); + simulateComplexFlutterAccessCheck('Bob', 20, 'admin', '/admin', 'write'); + simulateComplexFlutterAccessCheck('Charlie', 25, 'user', '/admin', 'write'); + simulateComplexFlutterAccessCheck('David', 19, 'user', '/data', 'read'); + + print('\n=== Examples Complete ==='); + print('\nNote: This demonstrates the same functionality described in the GitHub issue.'); + print('Users can now pass JSON strings directly to Casbin without creating custom classes!'); +} From ccc45dffb9cc3a5df3bee86cdeb4bab49a6428a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:45:55 +0000 Subject: [PATCH 5/7] Add test validating exact GitHub issue scenario Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com> --- test/github_issue_test.dart | 74 +++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/github_issue_test.dart diff --git a/test/github_issue_test.dart b/test/github_issue_test.dart new file mode 100644 index 0000000..aacd9d2 --- /dev/null +++ b/test/github_issue_test.dart @@ -0,0 +1,74 @@ +// Copyright 2018-2021 The Casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// This test validates the exact scenario described in the GitHub issue +/// to ensure the fix works as expected for the user's use case. + +import 'package:casbin/casbin.dart'; +import 'package:test/test.dart'; +import 'dart:convert'; + +void main() { + test('GitHub issue scenario - ABAC with JSON should work exactly as described', () { + // Exact model from the GitHub issue + final model = Model()..loadModelFromText(''' +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, condition + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = eval(p.condition) && r.obj == p.obj && r.act == p.act +'''); + + final enforcer = Enforcer.initWithModelAndAdapter(model); + + // Exact policy from the GitHub issue + final policyLine = 'p, {"age": 18}, /data1, read, r.sub.age >= 18 && r.sub.age < 60'; + final parts = policyLine.split(',').map((e) => e.trim()).toList(); + + // Remove the 'p' prefix and add the policy + enforcer.addPolicy([parts[1], parts[2], parts[3], parts[4]]); + + // Test with age 25 (should be allowed) + String subJson = jsonEncode({"age": 25}); + bool result = enforcer.enforce([subJson, '/data1', 'read']); + + expect(result, isTrue, reason: 'Age 25 should be allowed (18 <= 25 < 60)'); + + // Test with age 18 (boundary, should be allowed) + subJson = jsonEncode({"age": 18}); + result = enforcer.enforce([subJson, '/data1', 'read']); + expect(result, isTrue, reason: 'Age 18 should be allowed (boundary condition)'); + + // Test with age 17 (should be denied) + subJson = jsonEncode({"age": 17}); + result = enforcer.enforce([subJson, '/data1', 'read']); + expect(result, isFalse, reason: 'Age 17 should be denied (< 18)'); + + // Test with age 60 (should be denied) + subJson = jsonEncode({"age": 60}); + result = enforcer.enforce([subJson, '/data1', 'read']); + expect(result, isFalse, reason: 'Age 60 should be denied (>= 60)'); + + // Test with age 59 (boundary, should be allowed) + subJson = jsonEncode({"age": 59}); + result = enforcer.enforce([subJson, '/data1', 'read']); + expect(result, isTrue, reason: 'Age 59 should be allowed (18 <= 59 < 60)'); + }); +} From 305682bcc0f33bdf90f57ceaee6e7e1d89649ed0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:20:47 +0000 Subject: [PATCH 6/7] Remove tests, examples, and README changes - keep only core functionality Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com> --- README.md | 61 ------- example/abac_json_example.dart | 112 ------------- example/flutter_abac_example.dart | 266 ------------------------------ test/github_issue_test.dart | 74 --------- test/json_abac_test.dart | 235 -------------------------- 5 files changed, 748 deletions(-) delete mode 100644 example/abac_json_example.dart delete mode 100644 example/flutter_abac_example.dart delete mode 100644 test/github_issue_test.dart delete mode 100644 test/json_abac_test.dart diff --git a/README.md b/README.md index 07f2363..ebad2f2 100644 --- a/README.md +++ b/README.md @@ -128,67 +128,6 @@ https://casbin.org/docs/tutorials See [Policy management APIs](#policy-management) for more usage. -## ABAC with JSON strings - -Dart-Casbin supports using JSON strings as subjects for Attribute-Based Access Control (ABAC), making it easy to implement ABAC in Flutter and Dart applications without creating custom classes. - -```dart -import 'package:casbin/casbin.dart'; -import 'dart:convert'; - -// Define an ABAC model -final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - -// Create enforcer -final enforcer = Enforcer.initWithModelAndAdapter(model); - -// Add policy with attribute-based condition -enforcer.addPolicy([ - '{"age": 18}', - '/data1', - 'read', - 'r.sub.age >= 18 && r.sub.age < 60' -]); - -// Check access using JSON string -String userJson = jsonEncode({"age": 25}); -bool allowed = enforcer.enforce([userJson, '/data1', 'read']); -// Returns: true (access granted) -``` - -You can also use multiple attributes: - -```dart -// Add policy with multiple attributes -enforcer.addPolicy([ - '{"role": "admin"}', - '/admin', - 'write', - 'r.sub.role == "admin" && r.sub.age >= 21' -]); - -// Check access with multiple user attributes -String adminJson = jsonEncode({"role": "admin", "age": 25}); -bool allowed = enforcer.enforce([adminJson, '/admin', 'write']); -// Returns: true -``` - -For complete examples, see: -- [example/abac_json_example.dart](example/abac_json_example.dart) - Basic ABAC with JSON -- [example/flutter_abac_example.dart](example/flutter_abac_example.dart) - Flutter-specific examples - ## Policy management Casbin provides two sets of APIs to manage permissions: diff --git a/example/abac_json_example.dart b/example/abac_json_example.dart deleted file mode 100644 index effb56a..0000000 --- a/example/abac_json_example.dart +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018-2021 The Casbin Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:casbin/casbin.dart'; -import 'dart:convert'; - -/// This example demonstrates ABAC (Attribute-Based Access Control) -/// using JSON strings as subjects. -/// -/// This is particularly useful for Flutter/Dart applications where -/// you want to pass user attributes as JSON without creating -/// custom classes that implement AbacClass. -void main() { - print('=== ABAC with JSON Strings Example ===\n'); - - // Define the model with ABAC support - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - // Create an enforcer - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add policies with attribute-based conditions - print('Adding policies...'); - enforcer.addPolicy([ - '{"age": 18}', - '/data1', - 'read', - 'r.sub.age >= 18 && r.sub.age < 60' - ]); - - enforcer.addPolicy([ - '{"role": "admin"}', - '/admin', - 'write', - 'r.sub.role == "admin" && r.sub.age >= 21' - ]); - - print('Policies added.\n'); - - // Test Case 1: Valid age for /data1 - print('--- Test Case 1: User with age 25 accessing /data1 ---'); - String user1 = jsonEncode({"age": 25}); - bool result1 = enforcer.enforce([user1, '/data1', 'read']); - print('User: $user1'); - print('Resource: /data1, Action: read'); - print('Result: ${result1 ? "✅ Access Granted" : "❌ Access Denied"}\n'); - - // Test Case 2: Age below minimum - print('--- Test Case 2: User with age 16 accessing /data1 ---'); - String user2 = jsonEncode({"age": 16}); - bool result2 = enforcer.enforce([user2, '/data1', 'read']); - print('User: $user2'); - print('Resource: /data1, Action: read'); - print('Result: ${result2 ? "✅ Access Granted" : "❌ Access Denied"}\n'); - - // Test Case 3: Age above maximum - print('--- Test Case 3: User with age 65 accessing /data1 ---'); - String user3 = jsonEncode({"age": 65}); - bool result3 = enforcer.enforce([user3, '/data1', 'read']); - print('User: $user3'); - print('Resource: /data1, Action: read'); - print('Result: ${result3 ? "✅ Access Granted" : "❌ Access Denied"}\n'); - - // Test Case 4: Admin with sufficient age - print('--- Test Case 4: Admin with age 25 accessing /admin ---'); - String admin1 = jsonEncode({"role": "admin", "age": 25}); - bool result4 = enforcer.enforce([admin1, '/admin', 'write']); - print('User: $admin1'); - print('Resource: /admin, Action: write'); - print('Result: ${result4 ? "✅ Access Granted" : "❌ Access Denied"}\n'); - - // Test Case 5: Admin with insufficient age - print('--- Test Case 5: Admin with age 20 accessing /admin ---'); - String admin2 = jsonEncode({"role": "admin", "age": 20}); - bool result5 = enforcer.enforce([admin2, '/admin', 'write']); - print('User: $admin2'); - print('Resource: /admin, Action: write'); - print('Result: ${result5 ? "✅ Access Granted" : "❌ Access Denied"}\n'); - - // Test Case 6: Non-admin user - print('--- Test Case 6: Regular user accessing /admin ---'); - String user4 = jsonEncode({"role": "user", "age": 25}); - bool result6 = enforcer.enforce([user4, '/admin', 'write']); - print('User: $user4'); - print('Resource: /admin, Action: write'); - print('Result: ${result6 ? "✅ Access Granted" : "❌ Access Denied"}\n'); - - print('=== Example Complete ==='); -} diff --git a/example/flutter_abac_example.dart b/example/flutter_abac_example.dart deleted file mode 100644 index 8f3b550..0000000 --- a/example/flutter_abac_example.dart +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2018-2021 The Casbin Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/// This example demonstrates how to use ABAC with JSON strings in Flutter. -/// -/// This approach allows you to pass user attributes directly as JSON without -/// creating custom classes, making it ideal for Flutter applications that -/// receive user data from APIs or other sources. -/// -/// To use this in a Flutter app: -/// 1. Add casbin to your pubspec.yaml: `casbin: ^0.1.0` -/// 2. Import this code into your Flutter widget -/// 3. Call the checkAccess() method when you need to verify permissions -/// -/// Example Flutter widget code: -/// ```dart -/// import 'package:flutter/material.dart'; -/// import 'package:casbin/casbin.dart'; -/// import 'dart:convert'; -/// -/// class ABACScreen extends StatefulWidget { -/// @override -/// _ABACScreenState createState() => _ABACScreenState(); -/// } -/// -/// class _ABACScreenState extends State { -/// final TextEditingController ageController = TextEditingController(); -/// String resultMessage = ""; -/// -/// Future checkAccess() async { -/// int? age = int.tryParse(ageController.text); -/// if (age == null) { -/// setState(() { -/// resultMessage = "❌ Please enter a valid age."; -/// }); -/// return; -/// } -/// -/// // Create model -/// final model = Model()..loadModelFromText(getModel()); -/// final enforcer = Enforcer.initWithModelAndAdapter(model); -/// -/// // Add policy -/// enforcer.addPolicy([ -/// '{"age": 18}', -/// '/data1', -/// 'read', -/// 'r.sub.age >= 18 && r.sub.age < 60' -/// ]); -/// -/// try { -/// // Create JSON string with user attributes -/// String subJson = jsonEncode({"age": age}); -/// -/// // Enforce the policy -/// bool result = enforcer.enforce([subJson, '/data1', 'read']); -/// -/// setState(() { -/// resultMessage = result ? "✅ Access Granted" : "❌ Access Denied"; -/// }); -/// } catch (e) { -/// debugPrint("⚠️ Error: $e"); -/// setState(() { -/// resultMessage = "⚠️ Error: ${e.toString()}"; -/// }); -/// } -/// } -/// -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// appBar: AppBar(title: Text("Casbin ABAC Demo")), -/// body: Padding( -/// padding: EdgeInsets.all(20), -/// child: Column( -/// mainAxisAlignment: MainAxisAlignment.center, -/// children: [ -/// TextField( -/// controller: ageController, -/// keyboardType: TextInputType.number, -/// decoration: InputDecoration( -/// labelText: "Age", -/// border: OutlineInputBorder() -/// ), -/// ), -/// SizedBox(height: 20), -/// ElevatedButton( -/// onPressed: checkAccess, -/// child: Text("Check Access"), -/// ), -/// SizedBox(height: 20), -/// SelectableText( -/// resultMessage, -/// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold) -/// ), -/// ], -/// ), -/// ), -/// ); -/// } -/// } -/// -/// String getModel() { -/// return ''' -/// [request_definition] -/// r = sub, obj, act -/// -/// [policy_definition] -/// p = sub, obj, act, condition -/// -/// [policy_effect] -/// e = some(where (p.eft == allow)) -/// -/// [matchers] -/// m = eval(p.condition) && r.obj == p.obj && r.act == p.act -/// '''; -/// } -/// ``` -/// -/// Key points: -/// - Use jsonEncode() to convert Dart maps to JSON strings -/// - Pass the JSON string directly to enforcer.enforce() -/// - The JSON attributes (like "age") can be accessed in policy conditions using dot notation (r.sub.age) -/// - No need to create custom classes that implement AbacClass -/// -library abac_flutter_example; - -import 'package:casbin/casbin.dart'; -import 'dart:convert'; - -/// Simulates a Flutter app's access control check -void simulateFlutterAccessCheck(int userAge, String resource, String action) { - print('\n--- Simulating Flutter Access Check ---'); - print('User Age: $userAge'); - print('Resource: $resource'); - print('Action: $action'); - - // Create the model - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - // Create enforcer - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add policy (this would typically come from a database or config file) - enforcer.addPolicy([ - '{"age": 18}', - '/data1', - 'read', - 'r.sub.age >= 18 && r.sub.age < 60' - ]); - - // Create JSON string from user attributes - // In a real Flutter app, this data might come from an API response - String subJson = jsonEncode({"age": userAge}); - - // Check access - try { - bool hasAccess = enforcer.enforce([subJson, resource, action]); - print('Result: ${hasAccess ? "✅ Access Granted" : "❌ Access Denied"}'); - } catch (e) { - print('⚠️ Error: $e'); - } -} - -/// Example with multiple user attributes -void simulateComplexFlutterAccessCheck( - String userName, - int userAge, - String userRole, - String resource, - String action, -) { - print('\n--- Simulating Complex Flutter Access Check ---'); - print('User: $userName'); - print('Age: $userAge'); - print('Role: $userRole'); - print('Resource: $resource'); - print('Action: $action'); - - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add policies with multiple attribute conditions - enforcer.addPolicy([ - '{"name": "user", "age": 18, "role": "user"}', - '/admin', - 'write', - 'r.sub.role == "admin" && r.sub.age >= 21' - ]); - - enforcer.addPolicy([ - '{"name": "user", "age": 18, "role": "user"}', - '/data', - 'read', - 'r.sub.age >= 18' - ]); - - // In a Flutter app, this data typically comes from user authentication - String subJson = jsonEncode({ - "name": userName, - "age": userAge, - "role": userRole, - }); - - try { - bool hasAccess = enforcer.enforce([subJson, resource, action]); - print('Result: ${hasAccess ? "✅ Access Granted" : "❌ Access Denied"}'); - } catch (e) { - print('⚠️ Error: $e'); - } -} - -void main() { - print('=== Flutter ABAC Examples with JSON Strings ==='); - - // Simple examples - simulateFlutterAccessCheck(25, '/data1', 'read'); - simulateFlutterAccessCheck(16, '/data1', 'read'); - simulateFlutterAccessCheck(65, '/data1', 'read'); - - // Complex examples with multiple attributes - simulateComplexFlutterAccessCheck('Alice', 25, 'admin', '/admin', 'write'); - simulateComplexFlutterAccessCheck('Bob', 20, 'admin', '/admin', 'write'); - simulateComplexFlutterAccessCheck('Charlie', 25, 'user', '/admin', 'write'); - simulateComplexFlutterAccessCheck('David', 19, 'user', '/data', 'read'); - - print('\n=== Examples Complete ==='); - print('\nNote: This demonstrates the same functionality described in the GitHub issue.'); - print('Users can now pass JSON strings directly to Casbin without creating custom classes!'); -} diff --git a/test/github_issue_test.dart b/test/github_issue_test.dart deleted file mode 100644 index aacd9d2..0000000 --- a/test/github_issue_test.dart +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2018-2021 The Casbin Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/// This test validates the exact scenario described in the GitHub issue -/// to ensure the fix works as expected for the user's use case. - -import 'package:casbin/casbin.dart'; -import 'package:test/test.dart'; -import 'dart:convert'; - -void main() { - test('GitHub issue scenario - ABAC with JSON should work exactly as described', () { - // Exact model from the GitHub issue - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Exact policy from the GitHub issue - final policyLine = 'p, {"age": 18}, /data1, read, r.sub.age >= 18 && r.sub.age < 60'; - final parts = policyLine.split(',').map((e) => e.trim()).toList(); - - // Remove the 'p' prefix and add the policy - enforcer.addPolicy([parts[1], parts[2], parts[3], parts[4]]); - - // Test with age 25 (should be allowed) - String subJson = jsonEncode({"age": 25}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isTrue, reason: 'Age 25 should be allowed (18 <= 25 < 60)'); - - // Test with age 18 (boundary, should be allowed) - subJson = jsonEncode({"age": 18}); - result = enforcer.enforce([subJson, '/data1', 'read']); - expect(result, isTrue, reason: 'Age 18 should be allowed (boundary condition)'); - - // Test with age 17 (should be denied) - subJson = jsonEncode({"age": 17}); - result = enforcer.enforce([subJson, '/data1', 'read']); - expect(result, isFalse, reason: 'Age 17 should be denied (< 18)'); - - // Test with age 60 (should be denied) - subJson = jsonEncode({"age": 60}); - result = enforcer.enforce([subJson, '/data1', 'read']); - expect(result, isFalse, reason: 'Age 60 should be denied (>= 60)'); - - // Test with age 59 (boundary, should be allowed) - subJson = jsonEncode({"age": 59}); - result = enforcer.enforce([subJson, '/data1', 'read']); - expect(result, isTrue, reason: 'Age 59 should be allowed (18 <= 59 < 60)'); - }); -} diff --git a/test/json_abac_test.dart b/test/json_abac_test.dart deleted file mode 100644 index 5a4273d..0000000 --- a/test/json_abac_test.dart +++ /dev/null @@ -1,235 +0,0 @@ -import 'package:casbin/casbin.dart'; -import 'package:test/test.dart'; -import 'dart:convert'; - -void main() { - group('ABAC with JSON strings', () { - test('should allow access when age is in valid range', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add the policy from the issue - enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); - - // Test with JSON string - age 25 should be allowed - String subJson = jsonEncode({"age": 25}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isTrue); - }); - - test('should deny access when age is below minimum', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add the policy from the issue - enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); - - // Test with JSON string - age 16 should be denied - String subJson = jsonEncode({"age": 16}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isFalse); - }); - - test('should deny access when age is above maximum', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add the policy from the issue - enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); - - // Test with JSON string - age 65 should be denied - String subJson = jsonEncode({"age": 65}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isFalse); - }); - - test('should work with multiple attributes in JSON', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add policy with multiple attribute checks - enforcer.addPolicy(['{"age": 18, "role": "user"}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.role == "admin"']); - - // Test with JSON string - should be allowed - String subJson = jsonEncode({"age": 25, "role": "admin"}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isTrue); - }); - - test('should deny with wrong role attribute', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - // Add policy with multiple attribute checks - enforcer.addPolicy(['{"age": 18, "role": "user"}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.role == "admin"']); - - // Test with JSON string - should be denied due to role mismatch - String subJson = jsonEncode({"age": 25, "role": "user"}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isFalse); - }); - - test('should work with boundary value at minimum', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); - - // Test with age exactly at minimum boundary - String subJson = jsonEncode({"age": 18}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isTrue); - }); - - test('should deny with boundary value at maximum', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act, condition - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.condition) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - enforcer.addPolicy(['{"age": 18}', '/data1', 'read', 'r.sub.age >= 18 && r.sub.age < 60']); - - // Test with age exactly at maximum boundary (should be denied because < 60) - String subJson = jsonEncode({"age": 60}); - bool result = enforcer.enforce([subJson, '/data1', 'read']); - - expect(result, isFalse); - }); - }); - - group('ABAC with AbacClass (existing functionality)', () { - test('should still work with AbacClass objects', () { - final model = Model()..loadModelFromText(''' -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub_rule, obj, act - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act -'''); - - final enforcer = Enforcer.initWithModelAndAdapter(model); - - enforcer.addPolicy(['r.sub.Age > 18', '/data1', 'read']); - - // Test with AbacClass instance - final subject = TestSubject(25, 'Alice'); - bool result = enforcer.enforce([subject, '/data1', 'read']); - - expect(result, isTrue); - }); - }); -} - -// Test class that implements AbacClass -class TestSubject implements AbacClass { - final int Age; - final String Name; - - TestSubject(this.Age, this.Name); - - @override - Map toMap() { - return {'Age': Age, 'Name': Name}; - } -} From 0ca43222200f381801780dd279e53ea6cbc9a85d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:35:06 +0000 Subject: [PATCH 7/7] Fix code formatting to pass CI check Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com> --- lib/src/utils/utils.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index d1d599b..b8ae449 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -25,10 +25,12 @@ class CasbinEvaluator extends ExpressionEvaluator { @override dynamic evalMemberExpression( - MemberExpression expression, Map context) { + MemberExpression expression, + Map context, + ) { var objectValue = eval(expression.object, context); Map object; - + // Handle different types of objects if (objectValue is String) { // Try to parse as JSON @@ -51,10 +53,12 @@ class CasbinEvaluator extends ExpressionEvaluator { try { object = objectValue.toMap(); } catch (e) { - throw Exception('Object must be a JSON string, Map, or implement AbacClass: ${objectValue.runtimeType}'); + throw Exception( + 'Object must be a JSON string, Map, or implement AbacClass: ${objectValue.runtimeType}', + ); } } - + return object[expression.property.name]; } }