|
| 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 | +/// This example demonstrates how to use ABAC with JSON strings in Flutter. |
| 16 | +/// |
| 17 | +/// This approach allows you to pass user attributes directly as JSON without |
| 18 | +/// creating custom classes, making it ideal for Flutter applications that |
| 19 | +/// receive user data from APIs or other sources. |
| 20 | +/// |
| 21 | +/// To use this in a Flutter app: |
| 22 | +/// 1. Add casbin to your pubspec.yaml: `casbin: ^0.1.0` |
| 23 | +/// 2. Import this code into your Flutter widget |
| 24 | +/// 3. Call the checkAccess() method when you need to verify permissions |
| 25 | +/// |
| 26 | +/// Example Flutter widget code: |
| 27 | +/// ```dart |
| 28 | +/// import 'package:flutter/material.dart'; |
| 29 | +/// import 'package:casbin/casbin.dart'; |
| 30 | +/// import 'dart:convert'; |
| 31 | +/// |
| 32 | +/// class ABACScreen extends StatefulWidget { |
| 33 | +/// @override |
| 34 | +/// _ABACScreenState createState() => _ABACScreenState(); |
| 35 | +/// } |
| 36 | +/// |
| 37 | +/// class _ABACScreenState extends State<ABACScreen> { |
| 38 | +/// final TextEditingController ageController = TextEditingController(); |
| 39 | +/// String resultMessage = ""; |
| 40 | +/// |
| 41 | +/// Future<void> checkAccess() async { |
| 42 | +/// int? age = int.tryParse(ageController.text); |
| 43 | +/// if (age == null) { |
| 44 | +/// setState(() { |
| 45 | +/// resultMessage = "❌ Please enter a valid age."; |
| 46 | +/// }); |
| 47 | +/// return; |
| 48 | +/// } |
| 49 | +/// |
| 50 | +/// // Create model |
| 51 | +/// final model = Model()..loadModelFromText(getModel()); |
| 52 | +/// final enforcer = Enforcer.initWithModelAndAdapter(model); |
| 53 | +/// |
| 54 | +/// // Add policy |
| 55 | +/// enforcer.addPolicy([ |
| 56 | +/// '{"age": 18}', |
| 57 | +/// '/data1', |
| 58 | +/// 'read', |
| 59 | +/// 'r.sub.age >= 18 && r.sub.age < 60' |
| 60 | +/// ]); |
| 61 | +/// |
| 62 | +/// try { |
| 63 | +/// // Create JSON string with user attributes |
| 64 | +/// String subJson = jsonEncode({"age": age}); |
| 65 | +/// |
| 66 | +/// // Enforce the policy |
| 67 | +/// bool result = enforcer.enforce([subJson, '/data1', 'read']); |
| 68 | +/// |
| 69 | +/// setState(() { |
| 70 | +/// resultMessage = result ? "✅ Access Granted" : "❌ Access Denied"; |
| 71 | +/// }); |
| 72 | +/// } catch (e) { |
| 73 | +/// debugPrint("⚠️ Error: $e"); |
| 74 | +/// setState(() { |
| 75 | +/// resultMessage = "⚠️ Error: ${e.toString()}"; |
| 76 | +/// }); |
| 77 | +/// } |
| 78 | +/// } |
| 79 | +/// |
| 80 | +/// @override |
| 81 | +/// Widget build(BuildContext context) { |
| 82 | +/// return Scaffold( |
| 83 | +/// appBar: AppBar(title: Text("Casbin ABAC Demo")), |
| 84 | +/// body: Padding( |
| 85 | +/// padding: EdgeInsets.all(20), |
| 86 | +/// child: Column( |
| 87 | +/// mainAxisAlignment: MainAxisAlignment.center, |
| 88 | +/// children: [ |
| 89 | +/// TextField( |
| 90 | +/// controller: ageController, |
| 91 | +/// keyboardType: TextInputType.number, |
| 92 | +/// decoration: InputDecoration( |
| 93 | +/// labelText: "Age", |
| 94 | +/// border: OutlineInputBorder() |
| 95 | +/// ), |
| 96 | +/// ), |
| 97 | +/// SizedBox(height: 20), |
| 98 | +/// ElevatedButton( |
| 99 | +/// onPressed: checkAccess, |
| 100 | +/// child: Text("Check Access"), |
| 101 | +/// ), |
| 102 | +/// SizedBox(height: 20), |
| 103 | +/// SelectableText( |
| 104 | +/// resultMessage, |
| 105 | +/// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold) |
| 106 | +/// ), |
| 107 | +/// ], |
| 108 | +/// ), |
| 109 | +/// ), |
| 110 | +/// ); |
| 111 | +/// } |
| 112 | +/// } |
| 113 | +/// |
| 114 | +/// String getModel() { |
| 115 | +/// return ''' |
| 116 | +/// [request_definition] |
| 117 | +/// r = sub, obj, act |
| 118 | +/// |
| 119 | +/// [policy_definition] |
| 120 | +/// p = sub, obj, act, condition |
| 121 | +/// |
| 122 | +/// [policy_effect] |
| 123 | +/// e = some(where (p.eft == allow)) |
| 124 | +/// |
| 125 | +/// [matchers] |
| 126 | +/// m = eval(p.condition) && r.obj == p.obj && r.act == p.act |
| 127 | +/// '''; |
| 128 | +/// } |
| 129 | +/// ``` |
| 130 | +/// |
| 131 | +/// Key points: |
| 132 | +/// - Use jsonEncode() to convert Dart maps to JSON strings |
| 133 | +/// - Pass the JSON string directly to enforcer.enforce() |
| 134 | +/// - The JSON attributes (like "age") can be accessed in policy conditions using dot notation (r.sub.age) |
| 135 | +/// - No need to create custom classes that implement AbacClass |
| 136 | +/// |
| 137 | +library abac_flutter_example; |
| 138 | + |
| 139 | +import 'package:casbin/casbin.dart'; |
| 140 | +import 'dart:convert'; |
| 141 | + |
| 142 | +/// Simulates a Flutter app's access control check |
| 143 | +void simulateFlutterAccessCheck(int userAge, String resource, String action) { |
| 144 | + print('\n--- Simulating Flutter Access Check ---'); |
| 145 | + print('User Age: $userAge'); |
| 146 | + print('Resource: $resource'); |
| 147 | + print('Action: $action'); |
| 148 | + |
| 149 | + // Create the model |
| 150 | + final model = Model()..loadModelFromText(''' |
| 151 | +[request_definition] |
| 152 | +r = sub, obj, act |
| 153 | +
|
| 154 | +[policy_definition] |
| 155 | +p = sub, obj, act, condition |
| 156 | +
|
| 157 | +[policy_effect] |
| 158 | +e = some(where (p.eft == allow)) |
| 159 | +
|
| 160 | +[matchers] |
| 161 | +m = eval(p.condition) && r.obj == p.obj && r.act == p.act |
| 162 | +'''); |
| 163 | + |
| 164 | + // Create enforcer |
| 165 | + final enforcer = Enforcer.initWithModelAndAdapter(model); |
| 166 | + |
| 167 | + // Add policy (this would typically come from a database or config file) |
| 168 | + enforcer.addPolicy([ |
| 169 | + '{"age": 18}', |
| 170 | + '/data1', |
| 171 | + 'read', |
| 172 | + 'r.sub.age >= 18 && r.sub.age < 60' |
| 173 | + ]); |
| 174 | + |
| 175 | + // Create JSON string from user attributes |
| 176 | + // In a real Flutter app, this data might come from an API response |
| 177 | + String subJson = jsonEncode({"age": userAge}); |
| 178 | + |
| 179 | + // Check access |
| 180 | + try { |
| 181 | + bool hasAccess = enforcer.enforce([subJson, resource, action]); |
| 182 | + print('Result: ${hasAccess ? "✅ Access Granted" : "❌ Access Denied"}'); |
| 183 | + } catch (e) { |
| 184 | + print('⚠️ Error: $e'); |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +/// Example with multiple user attributes |
| 189 | +void simulateComplexFlutterAccessCheck( |
| 190 | + String userName, |
| 191 | + int userAge, |
| 192 | + String userRole, |
| 193 | + String resource, |
| 194 | + String action, |
| 195 | +) { |
| 196 | + print('\n--- Simulating Complex Flutter Access Check ---'); |
| 197 | + print('User: $userName'); |
| 198 | + print('Age: $userAge'); |
| 199 | + print('Role: $userRole'); |
| 200 | + print('Resource: $resource'); |
| 201 | + print('Action: $action'); |
| 202 | + |
| 203 | + final model = Model()..loadModelFromText(''' |
| 204 | +[request_definition] |
| 205 | +r = sub, obj, act |
| 206 | +
|
| 207 | +[policy_definition] |
| 208 | +p = sub, obj, act, condition |
| 209 | +
|
| 210 | +[policy_effect] |
| 211 | +e = some(where (p.eft == allow)) |
| 212 | +
|
| 213 | +[matchers] |
| 214 | +m = eval(p.condition) && r.obj == p.obj && r.act == p.act |
| 215 | +'''); |
| 216 | + |
| 217 | + final enforcer = Enforcer.initWithModelAndAdapter(model); |
| 218 | + |
| 219 | + // Add policies with multiple attribute conditions |
| 220 | + enforcer.addPolicy([ |
| 221 | + '{"name": "user", "age": 18, "role": "user"}', |
| 222 | + '/admin', |
| 223 | + 'write', |
| 224 | + 'r.sub.role == "admin" && r.sub.age >= 21' |
| 225 | + ]); |
| 226 | + |
| 227 | + enforcer.addPolicy([ |
| 228 | + '{"name": "user", "age": 18, "role": "user"}', |
| 229 | + '/data', |
| 230 | + 'read', |
| 231 | + 'r.sub.age >= 18' |
| 232 | + ]); |
| 233 | + |
| 234 | + // In a Flutter app, this data typically comes from user authentication |
| 235 | + String subJson = jsonEncode({ |
| 236 | + "name": userName, |
| 237 | + "age": userAge, |
| 238 | + "role": userRole, |
| 239 | + }); |
| 240 | + |
| 241 | + try { |
| 242 | + bool hasAccess = enforcer.enforce([subJson, resource, action]); |
| 243 | + print('Result: ${hasAccess ? "✅ Access Granted" : "❌ Access Denied"}'); |
| 244 | + } catch (e) { |
| 245 | + print('⚠️ Error: $e'); |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | +void main() { |
| 250 | + print('=== Flutter ABAC Examples with JSON Strings ==='); |
| 251 | + |
| 252 | + // Simple examples |
| 253 | + simulateFlutterAccessCheck(25, '/data1', 'read'); |
| 254 | + simulateFlutterAccessCheck(16, '/data1', 'read'); |
| 255 | + simulateFlutterAccessCheck(65, '/data1', 'read'); |
| 256 | + |
| 257 | + // Complex examples with multiple attributes |
| 258 | + simulateComplexFlutterAccessCheck('Alice', 25, 'admin', '/admin', 'write'); |
| 259 | + simulateComplexFlutterAccessCheck('Bob', 20, 'admin', '/admin', 'write'); |
| 260 | + simulateComplexFlutterAccessCheck('Charlie', 25, 'user', '/admin', 'write'); |
| 261 | + simulateComplexFlutterAccessCheck('David', 19, 'user', '/data', 'read'); |
| 262 | + |
| 263 | + print('\n=== Examples Complete ==='); |
| 264 | + print('\nNote: This demonstrates the same functionality described in the GitHub issue.'); |
| 265 | + print('Users can now pass JSON strings directly to Casbin without creating custom classes!'); |
| 266 | +} |
0 commit comments