Skip to content

Commit 52c302c

Browse files
authored
feat: Port GetAllowedObjectConditions() from Go Casbin (#493)
1 parent bb6cda3 commit 52c302c

File tree

6 files changed

+177
-0
lines changed

6 files changed

+177
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[request_definition]
2+
r = sub, obj, act
3+
4+
[policy_definition]
5+
p = sub, sub_rule, act
6+
7+
[role_definition]
8+
g = _, _
9+
10+
[policy_effect]
11+
e = some(where (p.eft == allow))
12+
13+
[matchers]
14+
m = g(r.sub, p.sub) && eval(p.sub_rule) && r.act == p.act
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
p, alice, r.obj.price < 25, read
2+
p, admin, r.obj.category_id = 2, read
3+
p, bob, r.obj.author = bob, write
4+
5+
g, alice, admin
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2018 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+
package org.casbin.jcasbin.exception;
16+
17+
public class CasbinEmptyConditionException extends RuntimeException {
18+
19+
public CasbinEmptyConditionException(String message) {
20+
super(message);
21+
}
22+
23+
public CasbinEmptyConditionException(String message, Throwable cause) {
24+
super(message, cause);
25+
}
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2018 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+
package org.casbin.jcasbin.exception;
16+
17+
public class CasbinObjConditionException extends RuntimeException {
18+
19+
public CasbinObjConditionException(String message) {
20+
super(message);
21+
}
22+
23+
public CasbinObjConditionException(String message, Throwable cause) {
24+
super(message, cause);
25+
}
26+
}

src/main/java/org/casbin/jcasbin/main/Enforcer.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
package org.casbin.jcasbin.main;
1616

17+
import org.casbin.jcasbin.exception.CasbinEmptyConditionException;
1718
import org.casbin.jcasbin.exception.CasbinNameNotExistException;
19+
import org.casbin.jcasbin.exception.CasbinObjConditionException;
1820
import org.casbin.jcasbin.model.Assertion;
1921
import org.casbin.jcasbin.model.FunctionMap;
2022
import org.casbin.jcasbin.model.Model;
@@ -644,4 +646,50 @@ public List<Boolean> batchEnforceWithMatcher(String matcher, List<List<String>>
644646
}
645647
return results;
646648
}
649+
650+
/**
651+
* getAllowedObjectConditions returns a list of object conditions that the user can access.
652+
* For example: conditions = e.getAllowedObjectConditions("alice", "read", "r.obj.")
653+
* Note:
654+
* <p>
655+
* 0. prefix: You can customize the prefix of the object conditions, and "r.obj." is commonly used as a prefix.
656+
* After removing the prefix, the remaining part is the condition of the object.
657+
* If there is an obj policy that does not meet the prefix requirement, a CasbinObjConditionException will be thrown.
658+
* <p>
659+
* 1. If the 'objectConditions' list is empty, throw CasbinEmptyConditionException
660+
* This exception is thrown because some data adapters' ORM return full table data by default
661+
* when they receive an empty condition, which tends to behave contrary to expectations.(e.g. GORM)
662+
* If you are using an adapter that does not behave like this, you can choose to ignore this exception.
663+
*
664+
* @param user the user.
665+
* @param action the action.
666+
* @param prefix the prefix of the object conditions.
667+
* @return a list of object conditions.
668+
* @throws CasbinObjConditionException if there is a policy that doesn't meet the prefix requirement.
669+
* @throws CasbinEmptyConditionException if the objectConditions list is empty.
670+
*/
671+
public List<String> getAllowedObjectConditions(String user, String action, String prefix) {
672+
if (user == null || action == null || prefix == null) {
673+
throw new IllegalArgumentException("user, action, and prefix cannot be null");
674+
}
675+
676+
List<List<String>> permissions = getImplicitPermissionsForUser(user);
677+
678+
List<String> objectConditions = new ArrayList<>();
679+
for (List<String> policy : permissions) {
680+
// policy {sub, obj, act}
681+
if (policy.size() >= 3 && policy.get(2).equals(action)) {
682+
if (!policy.get(1).startsWith(prefix)) {
683+
throw new CasbinObjConditionException("need to meet the prefix required by the object condition");
684+
}
685+
objectConditions.add(policy.get(1).substring(prefix.length()));
686+
}
687+
}
688+
689+
if (objectConditions.isEmpty()) {
690+
throw new CasbinEmptyConditionException("GetAllowedObjectConditions has an empty condition");
691+
}
692+
693+
return objectConditions;
694+
}
647695
}

src/test/java/org/casbin/jcasbin/main/RbacAPIUnitTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,62 @@ public void testImplicitUsersForRole() {
240240
testGetImplicitUsersForRole(e, "book_group", asList("/book/*", "/book/:id", "/book2/{id}"));
241241
testGetImplicitUsersForRole(e, "pen_group", asList("/pen/:id", "/pen2/{id}"));
242242
}
243+
244+
@Test
245+
public void testGetAllowedObjectConditions() {
246+
Enforcer e = new Enforcer("examples/object_conditions_model.conf", "examples/object_conditions_policy.csv");
247+
248+
// Test basic cases
249+
testGetAllowedObjectConditions(e, "alice", "read", "r.obj.", asList("price < 25", "category_id = 2"));
250+
testGetAllowedObjectConditions(e, "admin", "read", "r.obj.", asList("category_id = 2"));
251+
testGetAllowedObjectConditions(e, "bob", "write", "r.obj.", asList("author = bob"));
252+
253+
// Test null parameter validation
254+
testGetAllowedObjectConditionsWithException(e, null, "read", "r.obj.",
255+
IllegalArgumentException.class);
256+
testGetAllowedObjectConditionsWithException(e, "alice", null, "r.obj.",
257+
IllegalArgumentException.class);
258+
testGetAllowedObjectConditionsWithException(e, "alice", "read", null,
259+
IllegalArgumentException.class);
260+
261+
// Test ErrEmptyCondition
262+
testGetAllowedObjectConditionsWithException(e, "alice", "write", "r.obj.",
263+
org.casbin.jcasbin.exception.CasbinEmptyConditionException.class);
264+
testGetAllowedObjectConditionsWithException(e, "bob", "read", "r.obj.",
265+
org.casbin.jcasbin.exception.CasbinEmptyConditionException.class);
266+
267+
// Test ErrObjCondition - policy without proper prefix
268+
// should: e.addPolicy("alice", "r.obj.price > 50", "read")
269+
boolean ok = e.addPolicy("alice", "price > 50", "read");
270+
if (ok) {
271+
testGetAllowedObjectConditionsWithException(e, "alice", "read", "r.obj.",
272+
org.casbin.jcasbin.exception.CasbinObjConditionException.class);
273+
}
274+
275+
// Test with different prefix
276+
e.clearPolicy();
277+
e.getRoleManager().deleteLink("alice", "admin");
278+
e.addPolicies(asList(
279+
asList("alice", "r.book.price < 25", "read"),
280+
asList("admin", "r.book.category_id = 2", "read"),
281+
asList("bob", "r.book.author = bob", "write")
282+
));
283+
testGetAllowedObjectConditions(e, "alice", "read", "r.book.", asList("price < 25"));
284+
testGetAllowedObjectConditions(e, "admin", "read", "r.book.", asList("category_id = 2"));
285+
testGetAllowedObjectConditions(e, "bob", "write", "r.book.", asList("author = bob"));
286+
}
287+
288+
private void testGetAllowedObjectConditions(Enforcer e, String user, String action, String prefix, List<String> expectedConditions) {
289+
List<String> actualConditions = e.getAllowedObjectConditions(user, action, prefix);
290+
assertEquals(expectedConditions, actualConditions);
291+
}
292+
293+
private void testGetAllowedObjectConditionsWithException(Enforcer e, String user, String action, String prefix, Class<? extends RuntimeException> expectedExceptionClass) {
294+
try {
295+
e.getAllowedObjectConditions(user, action, prefix);
296+
fail("Expected exception " + expectedExceptionClass.getName() + " was not thrown");
297+
} catch (RuntimeException ex) {
298+
assertEquals(expectedExceptionClass, ex.getClass());
299+
}
300+
}
243301
}

0 commit comments

Comments
 (0)