2
2
3
3
import sqlalchemy
4
4
from casbin import persist
5
- from sqlalchemy import Column , Integer , String
6
- from sqlalchemy import create_engine , or_
5
+ from sqlalchemy import Column , Integer , String , Boolean
6
+ from sqlalchemy import create_engine , or_ , not_
7
7
from sqlalchemy .orm import sessionmaker
8
8
9
9
# declarative base class
@@ -56,15 +56,33 @@ class Filter:
56
56
class Adapter (persist .Adapter , persist .adapters .UpdateAdapter ):
57
57
"""the interface for Casbin adapters."""
58
58
59
- def __init__ (self , engine , db_class = None , filtered = False , create_all_models = True ):
59
+ def __init__ (
60
+ self ,
61
+ engine ,
62
+ db_class = None ,
63
+ db_class_softdelete_attribute = None ,
64
+ filtered = False ,
65
+ create_all_models = True ,
66
+ ):
60
67
if isinstance (engine , str ):
61
68
self ._engine = create_engine (engine )
62
69
else :
63
70
self ._engine = engine
64
71
72
+ self .softdelete_attribute = None
73
+
65
74
if db_class is None :
66
75
db_class = CasbinRule
67
76
else :
77
+ if db_class_softdelete_attribute is not None and not isinstance (
78
+ db_class_softdelete_attribute .type , Boolean
79
+ ):
80
+ msg = f"The type of db_class_softdelete_attribute needs to be { str (Boolean )!r} . "
81
+ msg += f"An attribute of type { str (type (db_class_softdelete_attribute .type ))!r} was given."
82
+ raise ValueError (msg )
83
+ # Softdelete is only supported when using custom class
84
+ self .softdelete_attribute = db_class_softdelete_attribute
85
+
68
86
for attr in (
69
87
"id" ,
70
88
"ptype" ,
@@ -102,7 +120,9 @@ def _session_scope(self):
102
120
def load_policy (self , model ):
103
121
"""loads all policy rules from the storage."""
104
122
with self ._session_scope () as session :
105
- lines = session .query (self ._db_class ).all ()
123
+ query = session .query (self ._db_class )
124
+ query = self ._softdelete_query (query )
125
+ lines = query .all ()
106
126
for line in lines :
107
127
persist .load_policy_line (str (line ), model )
108
128
@@ -113,6 +133,7 @@ def load_filtered_policy(self, model, filter) -> None:
113
133
"""loads all policy rules from the storage."""
114
134
with self ._session_scope () as session :
115
135
query = session .query (self ._db_class )
136
+ query = self ._softdelete_query (query )
116
137
filters = self .filter_query (query , filter )
117
138
filters = filters .all ()
118
139
@@ -140,15 +161,60 @@ def _save_policy_line(self, ptype, rule, session=None):
140
161
141
162
def save_policy (self , model ):
142
163
"""saves all policy rules to the storage."""
164
+
165
+ # Use the default strategy when soft delete is not enabled
166
+ if self .softdelete_attribute is None :
167
+ with self ._session_scope () as session :
168
+ query = session .query (self ._db_class )
169
+ query .delete ()
170
+ for sec in ["p" , "g" ]:
171
+ if sec not in model .model .keys ():
172
+ continue
173
+ for ptype , ast in model .model [sec ].items ():
174
+ for rule in ast .policy :
175
+ self ._save_policy_line (ptype , rule , session = session )
176
+ return True
177
+
178
+ # Custom stategy for softdelete since it does not make sense to recreate all of the
179
+ # entries when using soft delete
143
180
with self ._session_scope () as session :
144
181
query = session .query (self ._db_class )
145
- query .delete ()
182
+ query = self ._softdelete_query (query )
183
+
184
+ # Delete entries that are not part of the model anymore
185
+ lines_before_changes = query .all ()
186
+
187
+ # Create new entries in the database
146
188
for sec in ["p" , "g" ]:
147
189
if sec not in model .model .keys ():
148
190
continue
149
191
for ptype , ast in model .model [sec ].items ():
150
192
for rule in ast .policy :
151
- self ._save_policy_line (ptype , rule , session = session )
193
+ # Filter for rule in the database
194
+ filter_query = query .filter (self ._db_class .ptype == ptype )
195
+ for index , value in enumerate (rule ):
196
+ v_value = getattr (self ._db_class , "v{}" .format (index ))
197
+ filter_query = filter_query .filter (v_value == value )
198
+ # If the rule is not present, create an entry in the database
199
+ if filter_query .count () == 0 :
200
+ self ._save_policy_line (ptype , rule , session = session )
201
+
202
+ for line in lines_before_changes :
203
+ ptype = line .ptype
204
+ sec = ptype [0 ] # derived from persist.load_policy_line function
205
+ fields_with_None = [
206
+ line .v0 ,
207
+ line .v1 ,
208
+ line .v2 ,
209
+ line .v3 ,
210
+ line .v4 ,
211
+ line .v5 ,
212
+ ]
213
+ rule = [element for element in fields_with_None if element is not None ]
214
+ # If the the rule is not part of the model, set the deletion flag to True
215
+ if not model .has_policy (sec , ptype , rule ):
216
+ setattr (line , self .softdelete_attribute .name , True )
217
+
152
218
return True
153
219
154
220
def add_policy (self , sec , ptype , rule ):
@@ -164,10 +230,15 @@ def remove_policy(self, sec, ptype, rule):
164
230
"""removes a policy rule from the storage."""
165
231
with self ._session_scope () as session :
166
232
query = session .query (self ._db_class )
233
+ query = self ._softdelete_query (query )
167
234
query = query .filter (self ._db_class .ptype == ptype )
168
235
for i , v in enumerate (rule ):
169
236
query = query .filter (getattr (self ._db_class , "v{}" .format (i )) == v )
170
- r = query .delete ()
237
+
238
+ if self .softdelete_attribute is None :
239
+ r = query .delete ()
240
+ else :
241
+ r = query .update ({self .softdelete_attribute : True })
171
242
172
243
return True if r > 0 else False
173
244
@@ -177,20 +248,27 @@ def remove_policies(self, sec, ptype, rules):
177
248
return
178
249
with self ._session_scope () as session :
179
250
query = session .query (self ._db_class )
251
+ query = self ._softdelete_query (query )
180
252
query = query .filter (self ._db_class .ptype == ptype )
181
253
rules = zip (* rules )
182
254
for i , rule in enumerate (rules ):
183
255
query = query .filter (
184
256
or_ (getattr (self ._db_class , "v{}" .format (i )) == v for v in rule )
185
257
)
186
- query .delete ()
258
+
259
+ if self .softdelete_attribute is None :
260
+ query .delete ()
261
+ else :
262
+ query .update ({self .softdelete_attribute : True })
187
263
188
264
def remove_filtered_policy (self , sec , ptype , field_index , * field_values ):
189
265
"""removes policy rules that match the filter from the storage.
190
266
This is part of the Auto-Save feature.
191
267
"""
192
268
with self ._session_scope () as session :
193
- query = session .query (self ._db_class ).filter (self ._db_class .ptype == ptype )
269
+ query = session .query (self ._db_class )
270
+ query = self ._softdelete_query (query )
271
+ query = query .filter (self ._db_class .ptype == ptype )
194
272
195
273
if not (0 <= field_index <= 5 ):
196
274
return False
@@ -200,12 +278,16 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values):
200
278
if v != "" :
201
279
v_value = getattr (self ._db_class , "v{}" .format (field_index + i ))
202
280
query = query .filter (v_value == v )
203
- r = query .delete ()
281
+
282
+ if self .softdelete_attribute is None :
283
+ r = query .delete ()
284
+ else :
285
+ r = query .update ({self .softdelete_attribute : True })
204
286
205
287
return True if r > 0 else False
206
288
207
289
def update_policy (
208
- self , sec : str , ptype : str , old_rule : [str ], new_rule : [str ]
290
+ self , sec : str , ptype : str , old_rule : list [str ], new_rule : list [str ]
209
291
) -> None :
210
292
"""
211
293
Update the old_rule with the new_rule in the database (storage).
@@ -219,7 +301,9 @@ def update_policy(
219
301
"""
220
302
221
303
with self ._session_scope () as session :
222
- query = session .query (self ._db_class ).filter (self ._db_class .ptype == ptype )
304
+ query = session .query (self ._db_class )
305
+ query = self ._softdelete_query (query )
306
+ query = query .filter (self ._db_class .ptype == ptype )
223
307
224
308
# locate the old rule
225
309
for index , value in enumerate (old_rule ):
@@ -241,12 +325,8 @@ def update_policies(
241
325
self ,
242
326
sec : str ,
243
327
ptype : str ,
244
- old_rules : [
245
- [str ],
246
- ],
247
- new_rules : [
248
- [str ],
249
- ],
328
+ old_rules : list [list [str ]],
329
+ new_rules : list [list [str ]],
250
330
) -> None :
251
331
"""
252
332
Update the old_rules with the new_rules in the database (storage).
@@ -262,8 +342,8 @@ def update_policies(
262
342
self .update_policy (sec , ptype , old_rules [i ], new_rules [i ])
263
343
264
344
def update_filtered_policies (
265
- self , sec , ptype , new_rules : [ [str ]], field_index , * field_values
266
- ) -> [ [str ]]:
345
+ self , sec , ptype , new_rules : list [ list [str ]], field_index , * field_values
346
+ ) -> list [ list [str ]]:
267
347
"""update_filtered_policies updates all the policies on the basis of the filter."""
268
348
269
349
filter = Filter ()
@@ -278,16 +358,15 @@ def update_filtered_policies(
278
358
279
359
self ._update_filtered_policies (new_rules , filter )
280
360
281
- def _update_filtered_policies (self , new_rules , filter ) -> [ [str ]]:
361
+ def _update_filtered_policies (self , new_rules , filter ) -> list [ list [str ]]:
282
362
"""_update_filtered_policies updates all the policies on the basis of the filter."""
283
363
284
364
with self ._session_scope () as session :
285
-
286
365
# Load old policies
287
366
288
- query = session .query (self ._db_class ). filter (
289
- self ._db_class . ptype == filter . ptype
290
- )
367
+ query = session .query (self ._db_class )
368
+ query = self ._softdelete_query ( query )
369
+ query = query . filter ( self . _db_class . ptype == filter . ptype )
291
370
filtered_query = self .filter_query (query , filter )
292
371
old_rules = filtered_query .all ()
293
372
@@ -302,3 +381,9 @@ def _update_filtered_policies(self, new_rules, filter) -> [[str]]:
302
381
# return deleted rules
303
382
304
383
return old_rules
384
+
385
+ def _softdelete_query (self , query ):
386
+ query_softdelete = query
387
+ if self .softdelete_attribute is not None :
388
+ query_softdelete = query_softdelete .where (not_ (self .softdelete_attribute ))
389
+ return query_softdelete
0 commit comments