diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..16906fe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: [ push, pull_request ] + +jobs: + test-and-coverage: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 1.8 + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'zulu' + cache: 'maven' + server-id: ossrh + server-username: OSSRH_JIRA_USERNAME + server-password: OSSRH_JIRA_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Build with Maven + run: mvn clean test jacoco:report + + - name: Upload To Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: 20 + + - name: Semantic Release + run: | + npm install -g @conveyal/maven-semantic-release semantic-release + semantic-release --prepare @conveyal/maven-semantic-release --publish @semantic-release/github,@conveyal/maven-semantic-release --verify-conditions @semantic-release/github,@conveyal/maven-semantic-release --verify-release @conveyal/maven-semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_JIRA_USERNAME: ${{ secrets.OSSRH_JIRA_USERNAME }} + OSSRH_JIRA_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }} diff --git a/README.md b/README.md index 701ca79..6988994 100644 --- a/README.md +++ b/README.md @@ -1 +1,76 @@ -# session-role-manager \ No newline at end of file +# session-role-manager +[![codebeat badge](https://codebeat.co/badges/998c8e12-ffdd-4196-b2a2-8979d7f1ee8a)](https://codebeat.co/projects/github-com-jcasbin-session-role-manager-master) +[![build](https://github.com/jcasbin/session-role-manager/actions/workflows/ci.yml/badge.svg)](https://github.com/jcasbin/session-role-manager/actions) +[![codecov](https://codecov.io/github/jcasbin/session-role-manager/branch/master/graph/badge.svg?token=4YRFEQY7VK)](https://codecov.io/github/jcasbin/session-role-manager) +[![javadoc](https://javadoc.io/badge2/org.casbin/session-role-manager/javadoc.svg)](https://javadoc.io/doc/org.casbin/session-role-manager) +[![Maven Central](https://img.shields.io/maven-central/v/org.casbin/session-role-manager.svg)](https://mvnrepository.com/artifact/org.casbin/session-role-manager/latest) +[![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN) + +Session Role Manager is the [Session-based](https://en.wikipedia.org/wiki/Session_(computer_science)) role manager for [jCasbin](https://github.com/casbin/jcasbin). With this library, jCasbin can load session-based role hierarchy (user-role mapping) from jCasbin policy or save role hierarchy to it. The session is only active in the specified time range. + +## Installation +```xml + + org.casbin + session-role-manager + 1.0.0 + +``` + +## Example +```java +import org.casbin.jcasbin.main.Enforcer; +import org.casbin.jcasbin.persist.file_adapter.FileAdapter; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Example { + public static void main(String[] args) { + // Create a new Enforcer using the model path. The default role manager is used initially. + Enforcer e = new Enforcer("examples/rbac_model_with_sessions.conf"); + + // Manually set an adapter for the policy. + FileAdapter a = new FileAdapter("examples/rbac_policy_with_sessions.csv"); + e.setAdapter(a); + + // Use our custom role manager. + SessionRoleManager rm = new SessionRoleManager(10); + e.setRoleManager(rm); + + // If our role manager relies on Casbin policy (e.g., reading "g" policy rules), + // we need to set the role manager before loading the policy. + e.loadPolicy(); + + // Current role inheritance tree (Time ranges shown in parentheses): + // delta echo foxtrott + // \ / \ / + // (0-20) \ (5-15) / \ (10-20) / (10-12) + // \ / \ / + // bravo charlie + // \ / + // (0-10) \ / (5-15) + // \ / + // alpha + + // Test permissions for different time points + assertTrue(e.enforce("alpha", "data1", "read", "00")); + assertTrue(e.enforce("alpha", "data1", "read", "05")); + assertTrue(e.enforce("alpha", "data1", "read", "10")); + assertFalse(e.enforce("alpha", "data1", "read", "15")); + assertFalse(e.enforce("alpha", "data1", "read", "20")); + + assertFalse(e.enforce("alpha", "data2", "read", "00")); + assertTrue(e.enforce("alpha", "data2", "read", "05")); + assertTrue(e.enforce("alpha", "data2", "read", "10")); + assertTrue(e.enforce("alpha", "data2", "read", "15")); + assertFalse(e.enforce("alpha", "data2", "read", "20")); + + assertFalse(e.enforce("alpha", "data3", "read", "00")); + assertFalse(e.enforce("alpha", "data3", "read", "05")); + assertTrue(e.enforce("alpha", "data3", "read", "10")); + assertFalse(e.enforce("alpha", "data3", "read", "15")); + assertFalse(e.enforce("alpha", "data3", "read", "20")); + } +} + +``` \ No newline at end of file diff --git a/examples/rbac_model_with_sessions.conf b/examples/rbac_model_with_sessions.conf new file mode 100644 index 0000000..be48baa --- /dev/null +++ b/examples/rbac_model_with_sessions.conf @@ -0,0 +1,19 @@ +# Request definition +[request_definition] +r = sub, obj, act, time + +# Policy definition +[policy_definition] +p = sub, obj, act + +# Policy effect +[policy_effect] +e = some(where (p_eft == allow)) && !some(where (p_eft == deny)) + +# Role definition +[role_definition] +g = _, _, _, _ + +# Matchers +[matchers] +m = g(r.sub, p.sub, r.time) && r.obj == p.obj && r.act == p.act diff --git a/examples/rbac_policy_with_sessions.csv b/examples/rbac_policy_with_sessions.csv new file mode 100644 index 0000000..aedb791 --- /dev/null +++ b/examples/rbac_policy_with_sessions.csv @@ -0,0 +1,10 @@ +p, delta, data1, read +p, echo, data2, read +p, foxtrott, data3, read + +g, alpha, bravo, 00, 10 +g, alpha, charlie, 05, 15 +g, bravo, delta, 00, 20 +g, bravo, echo, 05, 15 +g, charlie, echo, 10, 20 +g, charlie, foxtrott, 10, 12 \ No newline at end of file diff --git a/maven-settings.xml b/maven-settings.xml new file mode 100644 index 0000000..9600d94 --- /dev/null +++ b/maven-settings.xml @@ -0,0 +1,22 @@ + + + + ossrh + ${OSSRH_JIRA_USERNAME} + ${OSSRH_JIRA_PASSWORD} + + + + + ossrh + + true + + + gpg + ${GPG_KEY_NAME} + ${GPG_PASSPHRASE} + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1cd8523 --- /dev/null +++ b/pom.xml @@ -0,0 +1,170 @@ + + + 4.0.0 + + org.casbin + session-role-manager + 1.0.0 + + Session based role manager for jCasbin + Load session-based role hierarchy from jCasbin policy or save role hierarchy to it + https://github.com/jcasbin/session-role-manager + 2024 + + + Github + https://github.com/jcasbin/session-role-manager/issues + + + + org.sonatype.oss + oss-parent + 7 + + + + + The Apache Software License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + https://github.com/jcasbin/session-role-manager + git@github.com:jcasbin/session-role-manager.git + https://github.com/hsluoyz + + + + + Xu Tan + 1428427011@qq.com + https://github.com/tx2002 + + + + + UTF-8 + + + + + default + + true + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.5.0 + true + + ossrh + true + true + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + package + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + org.jacoco + jacoco-maven-plugin + 0.7.6.201602180812 + + + prepare-agent + + prepare-agent + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + + ossrh + https://central.sonatype.com + + + + + + + + junit + junit + 4.13.1 + test + + + org.casbin + jcasbin + 1.31.2 + + + commons-collections + commons-collections + 3.2.2 + + + + diff --git a/src/main/java/org/casbin/rolemanager/Session.java b/src/main/java/org/casbin/rolemanager/Session.java new file mode 100644 index 0000000..bba1101 --- /dev/null +++ b/src/main/java/org/casbin/rolemanager/Session.java @@ -0,0 +1,39 @@ +// Copyright 2024 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. + +package org.casbin.rolemanager; + +public class Session { + private SessionRole role; + private String startTime; + private String endTime; + + public Session(SessionRole role, String startTime, String endTime) { + this.role = role; + this.startTime = startTime; + this.endTime = endTime; + } + + public SessionRole getRole() { + return role; + } + + public String getStartTime() { + return startTime; + } + + public String getEndTime() { + return endTime; + } +} diff --git a/src/main/java/org/casbin/rolemanager/SessionRole.java b/src/main/java/org/casbin/rolemanager/SessionRole.java new file mode 100644 index 0000000..63f4fc0 --- /dev/null +++ b/src/main/java/org/casbin/rolemanager/SessionRole.java @@ -0,0 +1,92 @@ +// Copyright 2024 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. + +package org.casbin.rolemanager; + +import java.util.ArrayList; +import java.util.List; + +public class SessionRole { + private String name; + private List sessions; + + public SessionRole(String name) { + this.name = name; + this.sessions = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void addSession(Session session) { + this.sessions.add(session); + } + + public void deleteSessions(String sessionName) { + sessions.removeIf(s -> s.getRole().getName().equals(sessionName)); + } + + public List getSessionRoles(String requestTime) { + List roles = new ArrayList<>(); + for (Session session : sessions) { + if (session.getStartTime().compareTo(requestTime) <= 0 && session.getEndTime().compareTo(requestTime) >= 0) { + if (!roles.contains(session.getRole().getName())) { + roles.add(session.getRole().getName()); + } + } + } + return roles; + } + + public boolean hasValidSession(String roleName, int hierarchyLevel, String requestTime) { + if (hierarchyLevel == 1) { + return this.name.equals(roleName); + } + + for (Session session : sessions) { + if (session.getStartTime().compareTo(requestTime) <= 0 && session.getEndTime().compareTo(requestTime) >= 0) { + if (session.getRole().getName().equals(roleName)) { + return true; + } + if (session.getRole().hasValidSession(roleName, hierarchyLevel - 1, requestTime)) { + return true; + } + } + } + return false; + } + + public boolean hasDirectRole(String roleName, String requestTime) { + for (Session session : sessions) { + if (session.getRole().getName().equals(roleName) && + session.getStartTime().compareTo(requestTime) <= 0 && + session.getEndTime().compareTo(requestTime) >= 0) { + return true; + } + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(name + " < "); + for (int i = 0; i < sessions.size(); i++) { + if (i > 0) sb.append(", "); + sb.append(sessions.get(i).getRole().getName()) + .append(" (until: ").append(sessions.get(i).getEndTime()).append(")"); + } + return sb.toString(); + } +} diff --git a/src/main/java/org/casbin/rolemanager/SessionRoleManager.java b/src/main/java/org/casbin/rolemanager/SessionRoleManager.java new file mode 100644 index 0000000..56a3095 --- /dev/null +++ b/src/main/java/org/casbin/rolemanager/SessionRoleManager.java @@ -0,0 +1,185 @@ +// Copyright 2024 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. + +package org.casbin.rolemanager; + +import org.casbin.jcasbin.rbac.RoleManager; +import java.util.*; + +/** + * SessionRoleManager is an implementation of the RoleManager interface that supports temporal role inheritance + * using sessions with a defined start and end time for role inheritance. + */ +public class SessionRoleManager implements RoleManager { + private Map allRoles; // Stores all roles with their respective session information + private int maxHierarchyLevel; // Maximum allowed hierarchy level for role inheritance + + /** + * Constructor for creating an instance of SessionRoleManager. + * + * @param maxHierarchyLevel The maximum depth of role hierarchy. + */ + public SessionRoleManager(int maxHierarchyLevel) { + this.allRoles = new HashMap<>(); + this.maxHierarchyLevel = maxHierarchyLevel; + } + + /** + * Checks if a role exists in the manager. + * + * @param name Name of the role. + * @return True if the role exists, false otherwise. + */ + private boolean hasRole(String name) { + return allRoles.containsKey(name); + } + + /** + * Creates a new role if it doesn't already exist. + * + * @param name Name of the role. + * @return The created or existing role. + */ + private SessionRole createRole(String name) { + if (!hasRole(name)) { + allRoles.put(name, new SessionRole(name)); + } + return allRoles.get(name); + } + + /** + * Clears all stored roles and resets the manager. + */ + @Override + public void clear() { + allRoles.clear(); + } + + /** + * Adds an inheritance link between two roles, valid for a specific time range. + * + * @param name1 Name of the first role (child role). + * @param name2 Name of the second role (parent role). + * @param timeRange The time range (start time, end time) for when the link is active. + * @throws IllegalArgumentException if the time range is not exactly 2 elements. + */ + @Override + public void addLink(String name1, String name2, String... timeRange) { + if (timeRange.length != 2) { + throw new IllegalArgumentException("Time range must consist of start and end times."); + } + String startTime = timeRange[0]; + String endTime = timeRange[1]; + + SessionRole role1 = createRole(name1); + SessionRole role2 = createRole(name2); + + Session session = new Session(role2, startTime, endTime); + role1.addSession(session); + } + + /** + * Deletes the inheritance link between two roles. + * + * @param name1 Name of the first role (child role). + * @param name2 Name of the second role (parent role). + */ + @Override + public void deleteLink(String name1, String name2, String... unused) { + if (!hasRole(name1) || !hasRole(name2)) { + throw new IllegalArgumentException("Role not found: " + name1 + " or " + name2); + } + + SessionRole role1 = createRole(name1); + role1.deleteSessions(name2); + } + + /** + * Checks if a role inherits another role at a specific time. + * + * @param name1 Name of the first role (child role). + * @param name2 Name of the second role (parent role). + * @param requestTime The time to check the role inheritance. + * @return True if role1 inherits role2 at the given time, false otherwise. + */ + @Override + public boolean hasLink(String name1, String name2, String... requestTime) { + if (requestTime.length != 1) { + throw new IllegalArgumentException("Request time must be specified."); + } + if (name1.equals(name2)) { + return true; + } + + if (!hasRole(name1) || !hasRole(name2)) { + return false; + } + + SessionRole role1 = createRole(name1); + return role1.hasValidSession(name2, maxHierarchyLevel, requestTime[0]); + } + + /** + * Gets all roles that a role inherits at a specific time. + * + * @param name The name of the role. + * @param currentTime The current time to check the role inheritance. + * @return A list of roles that the role inherits at the given time. + */ + @Override + public List getRoles(String name, String... currentTime) { + if (currentTime.length != 1) { + throw new IllegalArgumentException("Current time must be specified."); + } + String requestTime = currentTime[0]; + + if (!hasRole(name)) { + throw new IllegalArgumentException("Role not found: " + name); + } + + return createRole(name).getSessionRoles(requestTime); + } + + /** + * Gets all users that inherit a specific role at a given time. + * + * @param name The name of the role to check. + * @param currentTime The current time to check the role inheritance. + * @return A list of users that inherit the given role at the specified time. + */ + @Override + public List getUsers(String name, String... currentTime) { + if (currentTime.length != 1) { + throw new IllegalArgumentException("Current time must be specified."); + } + String requestTime = currentTime[0]; + + List users = new ArrayList<>(); + for (SessionRole role : allRoles.values()) { + if (role.hasDirectRole(name, requestTime)) { + users.add(role.getName()); + } + } + Collections.sort(users); + return users; + } + + /** + * Prints all the roles and their sessions. + */ + @Override + public void printRoles() { + allRoles.values().forEach(role -> System.out.println(role.toString())); + } +} diff --git a/src/test/java/org/casbin/rolemanager/SessionRoleManagerTest.java b/src/test/java/org/casbin/rolemanager/SessionRoleManagerTest.java new file mode 100644 index 0000000..9fd3f17 --- /dev/null +++ b/src/test/java/org/casbin/rolemanager/SessionRoleManagerTest.java @@ -0,0 +1,219 @@ +// Copyright 2024 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. + +package org.casbin.rolemanager; + +import org.casbin.jcasbin.main.Enforcer; +import org.casbin.jcasbin.persist.file_adapter.FileAdapter; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.*; + +public class SessionRoleManagerTest { + + private SessionRoleManager rm; + + @Before + public void setUp() { + // Initialize the role manager with a hierarchy level (3 here for example) + rm = new SessionRoleManager(3); + } + + // Helper functions to simulate time functions from the Go version + private String getCurrentTime() { + return String.valueOf(System.currentTimeMillis()); + } + + private String getInOneHour() { + return String.valueOf(System.currentTimeMillis() + 3600 * 1000); + } + + private String getOneHourAgo() { + return String.valueOf(System.currentTimeMillis() - 3600 * 1000); + } + + private String getAfterOneHour() { + return String.valueOf(System.currentTimeMillis() + 3600 * 1000 + 1); + } + + // Test the role inheritance with time-limited sessions + @Test + public void testSessionRole() { + rm.addLink("alpha", "bravo", getCurrentTime(), getInOneHour()); + rm.addLink("alpha", "charlie", getCurrentTime(), getInOneHour()); + rm.addLink("bravo", "delta", getCurrentTime(), getInOneHour()); + rm.addLink("bravo", "echo", getCurrentTime(), getInOneHour()); + rm.addLink("charlie", "echo", getCurrentTime(), getInOneHour()); + rm.addLink("charlie", "foxtrott", getCurrentTime(), getInOneHour()); + + assertTrue(rm.hasLink("alpha", "bravo", getCurrentTime())); + assertTrue(rm.hasLink("alpha", "charlie", getCurrentTime())); + assertTrue(rm.hasLink("bravo", "delta", getCurrentTime())); + assertTrue(rm.hasLink("bravo", "echo", getCurrentTime())); + assertTrue(rm.hasLink("charlie", "echo", getCurrentTime())); + assertTrue(rm.hasLink("charlie", "foxtrott", getCurrentTime())); + + assertFalse(rm.hasLink("alpha", "bravo", getOneHourAgo())); + assertFalse(rm.hasLink("alpha", "charlie", getOneHourAgo())); + assertFalse(rm.hasLink("bravo", "delta", getOneHourAgo())); + assertFalse(rm.hasLink("bravo", "echo", getOneHourAgo())); + assertFalse(rm.hasLink("charlie", "echo", getOneHourAgo())); + assertFalse(rm.hasLink("charlie", "foxtrott", getOneHourAgo())); + + assertFalse(rm.hasLink("alpha", "bravo", getAfterOneHour())); + assertFalse(rm.hasLink("alpha", "charlie", getAfterOneHour())); + assertFalse(rm.hasLink("bravo", "delta", getAfterOneHour())); + assertFalse(rm.hasLink("bravo", "echo", getAfterOneHour())); + assertFalse(rm.hasLink("charlie", "echo", getAfterOneHour())); + assertFalse(rm.hasLink("charlie", "foxtrott", getAfterOneHour())); + } + + // Test the Clear function to ensure all roles are cleared + @Test + public void testClear() { + rm.addLink("alpha", "bravo", getCurrentTime(), getInOneHour()); + rm.addLink("alpha", "charlie", getCurrentTime(), getInOneHour()); + rm.clear(); + + assertFalse(rm.hasLink("alpha", "bravo", getCurrentTime())); + assertFalse(rm.hasLink("alpha", "charlie", getCurrentTime())); + } + + @Test + public void testAddLink() { + rm.addLink("alpha", "bravo", getCurrentTime(), getInOneHour()); + assertTrue(rm.hasLink("alpha", "bravo", getCurrentTime())); + } + + // Test hasLink functionality for session roles + @Test + public void testHasLink() { + assertFalse(rm.hasLink("alpha", "bravo", getCurrentTime())); + assertTrue(rm.hasLink("alpha", "alpha", getCurrentTime())); + + rm.addLink("alpha", "bravo", getCurrentTime(), getInOneHour()); + assertTrue(rm.hasLink("alpha", "bravo", getCurrentTime())); + } + + // Test deleting specific links + @Test + public void testDeleteLink() { + rm.addLink("alpha", "bravo", getOneHourAgo(), getInOneHour()); + rm.deleteLink("alpha", "bravo"); + + assertFalse(rm.hasLink("alpha", "bravo", getCurrentTime())); + } + + // Test hierarchy level limits + @Test + public void testHierarchieLevel() { + rm = new SessionRoleManager(2); + rm.addLink("alpha", "bravo", getOneHourAgo(), getInOneHour()); + rm.addLink("bravo", "charlie", getOneHourAgo(), getInOneHour()); + + assertFalse(rm.hasLink("alpha", "charlie", getCurrentTime())); + rm = new SessionRoleManager(3); + rm.clear(); + rm.addLink("alpha", "bravo", getOneHourAgo(), getInOneHour()); + rm.addLink("bravo", "charlie", getOneHourAgo(), getInOneHour()); + + assertTrue(rm.hasLink("alpha", "charlie", getCurrentTime())); + } + + // Test expired session handling + @Test + public void testOutdatedSessions() { + rm.addLink("alpha", "bravo", getOneHourAgo(), getCurrentTime()); + rm.addLink("bravo", "charlie", getOneHourAgo(), getInOneHour()); + + assertFalse(rm.hasLink("alpha", "bravo", getInOneHour())); + assertTrue(rm.hasLink("alpha", "charlie", getOneHourAgo())); + } + + // Test role inheritance + @Test + public void testGetRoles() { + String oneHourAgo = getOneHourAgo(); + String currentTime = getCurrentTime(); + String inOneHour = getInOneHour(); + + rm.addLink("alpha", "bravo", oneHourAgo, inOneHour); + rm.addLink("alpha", "charlie", oneHourAgo, currentTime); + + assertEquals(Arrays.asList("bravo", "charlie"), rm.getRoles("alpha", oneHourAgo)); + assertEquals(Arrays.asList("bravo"), rm.getRoles("alpha", currentTime + 1)); + } + + // Test user-role mappings + @Test + public void testGetUsers() { + rm.addLink("bravo", "alpha", getOneHourAgo(), getInOneHour()); + rm.addLink("charlie", "alpha", getOneHourAgo(), getInOneHour()); + rm.addLink("delta", "alpha", getOneHourAgo(), getInOneHour()); + + assertEquals(Arrays.asList("bravo", "charlie", "delta"), rm.getUsers("alpha", getCurrentTime())); + } + + // Test the Enforcer with session-based roles and policies + @Test + public void testEnforcer() throws Exception { + // Create a new Enforcer using the model path. The default role manager is used initially. + Enforcer e = new Enforcer("examples/rbac_model_with_sessions.conf"); + + // Manually set an adapter for the policy. + FileAdapter a = new FileAdapter("examples/rbac_policy_with_sessions.csv"); + e.setAdapter(a); + + // Use our custom role manager. + SessionRoleManager rm = new SessionRoleManager(10); + e.setRoleManager(rm); + + // If our role manager relies on Casbin policy (e.g., reading "g" policy rules), + // we need to set the role manager before loading the policy. + e.loadPolicy(); + + // Current role inheritance tree (Time ranges shown in parentheses): + // delta echo foxtrott + // \ / \ / + // (0-20) \ (5-15) / \ (10-20) / (10-12) + // \ / \ / + // bravo charlie + // \ / + // (0-10) \ / (5-15) + // \ / + // alpha + + // Test permissions for different time points + assertTrue(e.enforce("alpha", "data1", "read", "00")); + assertTrue(e.enforce("alpha", "data1", "read", "05")); + assertTrue(e.enforce("alpha", "data1", "read", "10")); + assertFalse(e.enforce("alpha", "data1", "read", "15")); + assertFalse(e.enforce("alpha", "data1", "read", "20")); + + assertFalse(e.enforce("alpha", "data2", "read", "00")); + assertTrue(e.enforce("alpha", "data2", "read", "05")); + assertTrue(e.enforce("alpha", "data2", "read", "10")); + assertTrue(e.enforce("alpha", "data2", "read", "15")); + assertFalse(e.enforce("alpha", "data2", "read", "20")); + + assertFalse(e.enforce("alpha", "data3", "read", "00")); + assertFalse(e.enforce("alpha", "data3", "read", "05")); + assertTrue(e.enforce("alpha", "data3", "read", "10")); + assertFalse(e.enforce("alpha", "data3", "read", "15")); + assertFalse(e.enforce("alpha", "data3", "read", "20")); + } +}