diff --git a/README.md b/README.md index 530da21..329a7c1 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,8 @@ For Maven: ```java package com.company.test; -import org.casbin.jcasbin.main.Enforcer; -import org.casbin.jcasbin.util.Util; import org.casbin.adapter.JDBCAdapter; +import org.casbin.jcasbin.main.Enforcer; import com.mysql.cj.jdbc.MysqlDataSource; public class Test { diff --git a/examples/rbac_with_domains_model.conf b/examples/rbac_with_domains_model.conf new file mode 100644 index 0000000..57c3721 --- /dev/null +++ b/examples/rbac_with_domains_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, dom, obj, act + +[policy_definition] +p = sub, dom, obj, act + +[role_definition] +g = _, _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/examples/rbac_with_domains_policy.csv b/examples/rbac_with_domains_policy.csv new file mode 100644 index 0000000..8558d17 --- /dev/null +++ b/examples/rbac_with_domains_policy.csv @@ -0,0 +1,6 @@ +p, admin, domain1, data1, read +p, admin, domain1, data1, write +p, admin, domain2, data2, read +p, admin, domain2, data2, write +g, alice, admin, domain1 +g, bob, admin, domain2 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5b10140..c9fdc94 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ org.casbin jcasbin - 1.1.0 + 1.6.2 mysql diff --git a/src/main/java/org/casbin/adapter/JDBCAdapter.java b/src/main/java/org/casbin/adapter/JDBCAdapter.java index eac56e9..f3d0835 100644 --- a/src/main/java/org/casbin/adapter/JDBCAdapter.java +++ b/src/main/java/org/casbin/adapter/JDBCAdapter.java @@ -1,4 +1,4 @@ -// Copyright 2018 The casbin Authors. All Rights Reserved. +// Copyright 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. @@ -14,35 +14,28 @@ package org.casbin.adapter; -import org.apache.commons.collections.CollectionUtils; -import org.casbin.jcasbin.model.Assertion; +import org.casbin.jcasbin.exception.CasbinAdapterException; import org.casbin.jcasbin.model.Model; -import org.casbin.jcasbin.persist.Adapter; +import org.casbin.jcasbin.persist.FilteredAdapter; import org.casbin.jcasbin.persist.Helper; +import org.casbin.jcasbin.persist.file_adapter.FilteredAdapter.Filter; import javax.sql.DataSource; -import java.sql.*; -import java.util.*; - -class CasbinRule { - int id; //Fields reserved for compatibility with other adapters, and the primary key is automatically incremented. - String ptype; - String v0; - String v1; - String v2; - String v3; - String v4; - String v5; -} +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; /** - * JDBCAdapter is the JDBC adapter for jCasbin. - * It can load policy from JDBC supported database or save policy to it. + * JDBCFilteredAdapter is the JDBC adapter for jCasbin. + * JDBCFilteredAdapter can load policy from JDBC supported database or save policy to it and it supports filtered policies . + * + * @author shy + * @since 2021/01/26 */ -public class JDBCAdapter implements Adapter { - private DataSource dataSource; - private final int batchSize = 1000; - private Connection conn; +public class JDBCAdapter extends JDBCBaseAdapter implements FilteredAdapter { + + private boolean isFiltered = true; /** * JDBCAdapter is the constructor for JDBCAdapter. @@ -53,7 +46,7 @@ public class JDBCAdapter implements Adapter { * @param password the password of the database. */ public JDBCAdapter(String driver, String url, String username, String password) throws Exception { - this(new JDBCDataSource(driver, url, username, password)); + super(driver, url, username, password); } /** @@ -62,99 +55,50 @@ public JDBCAdapter(String driver, String url, String username, String password) * @param dataSource the JDBC DataSource. */ public JDBCAdapter(DataSource dataSource) throws Exception { - this.dataSource = dataSource; - migrate(); + super(dataSource); } - private void migrate() throws SQLException { - conn = dataSource.getConnection(); - Statement stmt = conn.createStatement(); - String sql = "CREATE TABLE IF NOT EXISTS casbin_rule(id int NOT NULL PRIMARY KEY auto_increment, ptype VARCHAR(100) NOT NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))"; - String productName = conn.getMetaData().getDatabaseProductName(); - - switch (productName) { - case "Oracle": - sql = "declare begin execute immediate 'CREATE TABLE CASBIN_RULE(id NUMBER(5, 0) not NULL primary key, ptype VARCHAR(100) not NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))'; " + - "exception when others then " + - "if SQLCODE = -955 then " + - "null; " + - "else raise; " + - "end if; " + - "end;"; - break; - case "Microsoft SQL Server": - sql = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='casbin_rule' and xtype='U') CREATE TABLE casbin_rule(id int NOT NULL primary key identity(1, 1), ptype VARCHAR(100) NOT NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))"; - break; - case "PostgreSQL": - sql = "CREATE SEQUENCE IF NOT EXISTS CASBIN_SEQUENCE START 1;"; - break; - } - - stmt.executeUpdate(sql); - if (productName.equals("Oracle")) { - sql = "declare " + - "V_NUM number;" + - "BEGIN " + - "V_NUM := 0; " + - "select count(0) into V_NUM from user_sequences where sequence_name = 'CASBIN_SEQUENCE';" + - "if V_NUM > 0 then " + - "null;" + - "else " + - "execute immediate 'CREATE SEQUENCE casbin_sequence increment by 1 start with 1 nomaxvalue nocycle nocache';" + - "end if;END;"; - stmt.executeUpdate(sql); - sql = "declare " + - "V_NUM number;" + - "BEGIN " + - "V_NUM := 0;" + - "select count(0) into V_NUM from user_triggers where trigger_name = 'CASBIN_ID_AUTOINCREMENT';" + - "if V_NUM > 0 then " + - "null;" + - "else " + - "execute immediate 'create trigger casbin_id_autoincrement before "+ - " insert on CASBIN_RULE for each row "+ - " when (new.id is null) "+ - " begin "+ - " select casbin_sequence.nextval into:new.id from dual;"+ - " end;';" + - "end if;" + - "END;"; - stmt.executeUpdate(sql); - } else if (productName.equals("PostgreSQL")) { - sql = "CREATE TABLE IF NOT EXISTS casbin_rule(id int NOT NULL PRIMARY KEY default nextval('CASBIN_SEQUENCE'::regclass), ptype VARCHAR(100) NOT NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))"; - stmt.executeUpdate(sql); - } - } - private void loadPolicyLine(CasbinRule line, Model model) { - String lineText = line.ptype; - if (!line.v0.equals("")) { - lineText += ", " + line.v0; - } - if (!line.v1.equals("")) { - lineText += ", " + line.v1; - } - if (!line.v2.equals("")) { - lineText += ", " + line.v2; - } - if (!line.v3.equals("")) { - lineText += ", " + line.v3; + /** + * loadFilteredPolicy loads only policy rules that match the filter. + * + * @param model the model. + * @param filter the filter used to specify which type of policy should be loaded. + * @throws CasbinAdapterException if the file path or the type of the filter is incorrect. + */ + @Override + public void loadFilteredPolicy(Model model, Object filter) throws CasbinAdapterException { + if (filter == null) { + loadPolicy(model); + isFiltered = false; + return; } - if (!line.v4.equals("")) { - lineText += ", " + line.v4; + if (!(filter instanceof Filter)) { + isFiltered = false; + throw new CasbinAdapterException("Invalid filter type."); } - if (!line.v5.equals("")) { - lineText += ", " + line.v5; + try { + loadFilteredPolicyFile(model, (Filter) filter, Helper::loadPolicyLine); + isFiltered = true; + } catch (Exception e) { + e.printStackTrace(); } - - Helper.loadPolicyLine(lineText, model); } /** - * loadPolicy loads all policy rules from the storage. + * IsFiltered returns true if the loaded policy has been filtered. + * + * @return true if have any filter roles. */ @Override - public void loadPolicy(Model model) { + public boolean isFiltered() { + return isFiltered; + } + + /** + * loadFilteredPolicyFile loads only policy rules that match the filter from file. + */ + private void loadFilteredPolicyFile(Model model, Filter filter, Helper.loadPolicyLineHandler handler) throws CasbinAdapterException { try (Statement stmt = conn.createStatement()) { ResultSet rSet = stmt.executeQuery("SELECT * FROM casbin_rule"); ResultSetMetaData rData = rSet.getMetaData(); @@ -177,6 +121,9 @@ public void loadPolicy(Model model) { line.v5 = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); } } + if (filterLine(line, filter)) { + continue; + } loadPolicyLine(line, model); } rSet.close(); @@ -186,177 +133,41 @@ public void loadPolicy(Model model) { } } - private CasbinRule savePolicyLine(String ptype, List rule) { - CasbinRule line = new CasbinRule(); - - line.ptype = ptype; - if (rule.size() > 0) { - line.v0 = rule.get(0); - } - if (rule.size() > 1) { - line.v1 = rule.get(1); - } - if (rule.size() > 2) { - line.v2 = rule.get(2); - } - if (rule.size() > 3) { - line.v3 = rule.get(3); - } - if (rule.size() > 4) { - line.v4 = rule.get(4); - } - if (rule.size() > 5) { - line.v5 = rule.get(5); - } - - return line; - } - /** - * savePolicy saves all policy rules to the storage. + * match the line. */ - @Override - public void savePolicy(Model model) { - String cleanSql = "delete from casbin_rule"; - String addSql = "INSERT INTO casbin_rule (ptype,v0,v1,v2,v3,v4,v5) VALUES(?,?,?,?,?,?,?)"; - - try { - conn.setAutoCommit(false); - - int count = 0; - - try (Statement statement = conn.createStatement(); PreparedStatement ps = conn.prepareStatement(addSql)) { - statement.execute(cleanSql); - for (Map.Entry entry : model.model.get("p").entrySet()) { - String ptype = entry.getKey(); - Assertion ast = entry.getValue(); - - for (List rule : ast.policy) { - CasbinRule line = savePolicyLine(ptype, rule); - - ps.setString(1, line.ptype); - ps.setString(2, line.v0); - ps.setString(3, line.v1); - ps.setString(4, line.v2); - ps.setString(5, line.v3); - ps.setString(6, line.v4); - ps.setString(7, line.v5); - - ps.addBatch(); - if (++count % batchSize == 0) { - ps.executeBatch(); - } - } - } - - for (Map.Entry entry : model.model.get("g").entrySet()) { - String ptype = entry.getKey(); - Assertion ast = entry.getValue(); - - for (List rule : ast.policy) { - CasbinRule line = savePolicyLine(ptype, rule); - - ps.setString(1, line.ptype); - ps.setString(2, line.v0); - ps.setString(3, line.v1); - ps.setString(4, line.v2); - ps.setString(5, line.v3); - ps.setString(6, line.v4); - ps.setString(7, line.v5); - - ps.addBatch(); - if (++count % batchSize == 0) { - ps.executeBatch(); - } - } - } - - ps.executeBatch(); - - conn.commit(); - } catch (SQLException e) { - conn.rollback(); - - e.printStackTrace(); - throw new Error(e); - } finally { - conn.setAutoCommit(true); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new Error(e); + private boolean filterLine(CasbinRule line, Filter filter) { + if (filter == null) { + return false; + } + String[] filterSlice = null; + switch (line.ptype.trim()) { + case "p": + filterSlice = filter.p; + break; + case "g": + filterSlice = filter.g; + break; } - } - - /** - * addPolicy adds a policy rule to the storage. - */ - @Override - public void addPolicy(String sec, String ptype, List rule) { - if (CollectionUtils.isEmpty(rule)) return; - - String sql = "INSERT INTO casbin_rule (ptype,v0,v1,v2,v3,v4,v5) VALUES(?,?,?,?,?,?,?)"; - - try(PreparedStatement ps = conn.prepareStatement(sql)) { - CasbinRule line = savePolicyLine(ptype, rule); - - ps.setString(1, line.ptype); - ps.setString(2, line.v0); - ps.setString(3, line.v1); - ps.setString(4, line.v2); - ps.setString(5, line.v3); - ps.setString(6, line.v4); - ps.setString(7, line.v5); - ps.addBatch(); - ps.executeBatch(); - } catch (SQLException e) { - e.printStackTrace(); - throw new Error(e); + if (filterSlice == null) { + filterSlice = new String[]{}; } + return filterWords(line.toStringArray(), filterSlice); } /** - * removePolicy removes a policy rule from the storage. + * match the words in the specific line. */ - @Override - public void removePolicy(String sec, String ptype, List rule) { - if (CollectionUtils.isEmpty(rule)) return; - removeFilteredPolicy(sec, ptype, 0, rule.toArray(new String[0])); - } - - /** - * removeFilteredPolicy removes policy rules that match the filter from the storage. - */ - @Override - public void removeFilteredPolicy(String sec, String ptype, int fieldIndex, String... fieldValues) { - List values = Optional.of(Arrays.asList(fieldValues)).orElse(new ArrayList<>()); - if (CollectionUtils.isEmpty(values)) return; - String sql = "DELETE FROM casbin_rule WHERE ptype = ?"; - int columnIndex = fieldIndex; - for (int i = 0; i < values.size(); i++) { - sql = String.format("%s%s%s%s", sql, " AND v", columnIndex, " = ?"); - columnIndex++; - } - - try (PreparedStatement ps = conn.prepareStatement(sql)) { - ps.setString(1, ptype); - for (int j = 0; j < values.size(); j++) { - ps.setString(j + 2, values.get(j)); + private boolean filterWords(String[] line, String[] filter) { + boolean skipLine = false; + int i = 0; + for (String s : filter) { + i++; + if (s.length() > 0 && !s.trim().equals(line[i].trim())) { + skipLine = true; + break; } - - ps.addBatch(); - - ps.executeBatch(); - } catch (SQLException e) { - e.printStackTrace(); - throw new Error(e); } - } - - /** - * Close the Connection. - */ - public void close() throws SQLException { - conn.close(); + return skipLine; } } diff --git a/src/main/java/org/casbin/adapter/JDBCBaseAdapter.java b/src/main/java/org/casbin/adapter/JDBCBaseAdapter.java new file mode 100644 index 0000000..79c1a34 --- /dev/null +++ b/src/main/java/org/casbin/adapter/JDBCBaseAdapter.java @@ -0,0 +1,368 @@ +// Copyright 2018 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.adapter; + +import org.apache.commons.collections.CollectionUtils; +import org.casbin.jcasbin.main.CoreEnforcer; +import org.casbin.jcasbin.model.Assertion; +import org.casbin.jcasbin.model.Model; +import org.casbin.jcasbin.persist.Adapter; +import org.casbin.jcasbin.persist.Helper; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.*; + +class CasbinRule { + int id; //Fields reserved for compatibility with other adapters, and the primary key is automatically incremented. + String ptype; + String v0; + String v1; + String v2; + String v3; + String v4; + String v5; + + public String[] toStringArray() { + return new String[]{ptype, v0, v1, v2, v3, v4, v5}; + } +} + +/** + * JDBCAdapter is the JDBC adapter for jCasbin. + * It can load policy from JDBC supported database or save policy to it. + */ +abstract class JDBCBaseAdapter implements Adapter { + private DataSource dataSource; + private final int batchSize = 1000; + protected Connection conn; + + /** + * JDBCAdapter is the constructor for JDBCAdapter. + * + * @param driver the JDBC driver, like "com.mysql.cj.jdbc.Driver". + * @param url the JDBC URL, like "jdbc:mysql://localhost:3306/casbin". + * @param username the username of the database. + * @param password the password of the database. + */ + protected JDBCBaseAdapter(String driver, String url, String username, String password) throws Exception { + this(new JDBCDataSource(driver, url, username, password)); + } + + /** + * The constructor for JDBCAdapter, will not try to create database. + * + * @param dataSource the JDBC DataSource. + */ + protected JDBCBaseAdapter(DataSource dataSource) throws Exception { + this.dataSource = dataSource; + migrate(); + } + + + protected void migrate() throws SQLException { + conn = dataSource.getConnection(); + Statement stmt = conn.createStatement(); + String sql = "CREATE TABLE IF NOT EXISTS casbin_rule(id int NOT NULL PRIMARY KEY auto_increment, ptype VARCHAR(100) NOT NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))"; + String productName = conn.getMetaData().getDatabaseProductName(); + + switch (productName) { + case "Oracle": + sql = "declare begin execute immediate 'CREATE TABLE CASBIN_RULE(id NUMBER(5, 0) not NULL primary key, ptype VARCHAR(100) not NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))'; " + + "exception when others then " + + "if SQLCODE = -955 then " + + "null; " + + "else raise; " + + "end if; " + + "end;"; + break; + case "Microsoft SQL Server": + sql = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='casbin_rule' and xtype='U') CREATE TABLE casbin_rule(id int NOT NULL primary key identity(1, 1), ptype VARCHAR(100) NOT NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))"; + break; + case "PostgreSQL": + sql = "CREATE SEQUENCE IF NOT EXISTS CASBIN_SEQUENCE START 1;"; + break; + } + + stmt.executeUpdate(sql); + if (productName.equals("Oracle")) { + sql = "declare " + + "V_NUM number;" + + "BEGIN " + + "V_NUM := 0; " + + "select count(0) into V_NUM from user_sequences where sequence_name = 'CASBIN_SEQUENCE';" + + "if V_NUM > 0 then " + + "null;" + + "else " + + "execute immediate 'CREATE SEQUENCE casbin_sequence increment by 1 start with 1 nomaxvalue nocycle nocache';" + + "end if;END;"; + stmt.executeUpdate(sql); + sql = "declare " + + "V_NUM number;" + + "BEGIN " + + "V_NUM := 0;" + + "select count(0) into V_NUM from user_triggers where trigger_name = 'CASBIN_ID_AUTOINCREMENT';" + + "if V_NUM > 0 then " + + "null;" + + "else " + + "execute immediate 'create trigger casbin_id_autoincrement before "+ + " insert on CASBIN_RULE for each row "+ + " when (new.id is null) "+ + " begin "+ + " select casbin_sequence.nextval into:new.id from dual;"+ + " end;';" + + "end if;" + + "END;"; + stmt.executeUpdate(sql); + } else if (productName.equals("PostgreSQL")) { + sql = "CREATE TABLE IF NOT EXISTS casbin_rule(id int NOT NULL PRIMARY KEY default nextval('CASBIN_SEQUENCE'::regclass), ptype VARCHAR(100) NOT NULL, v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100), v3 VARCHAR(100), v4 VARCHAR(100), v5 VARCHAR(100))"; + stmt.executeUpdate(sql); + } + } + + protected void loadPolicyLine(CasbinRule line, Model model) { + String lineText = line.ptype; + if (!line.v0.equals("")) { + lineText += ", " + line.v0; + } + if (!line.v1.equals("")) { + lineText += ", " + line.v1; + } + if (!line.v2.equals("")) { + lineText += ", " + line.v2; + } + if (!line.v3.equals("")) { + lineText += ", " + line.v3; + } + if (!line.v4.equals("")) { + lineText += ", " + line.v4; + } + if (!line.v5.equals("")) { + lineText += ", " + line.v5; + } + + Helper.loadPolicyLine(lineText, model); + } + + /** + * loadPolicy loads all policy rules from the storage. + */ + @Override + public void loadPolicy(Model model) { + try (Statement stmt = conn.createStatement()) { + ResultSet rSet = stmt.executeQuery("SELECT * FROM casbin_rule"); + ResultSetMetaData rData = rSet.getMetaData(); + while (rSet.next()) { + CasbinRule line = new CasbinRule(); + for (int i = 1; i <= rData.getColumnCount(); i++) { + if (i == 2) { + line.ptype = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); + } else if (i == 3) { + line.v0 = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); + } else if (i == 4) { + line.v1 = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); + } else if (i == 5) { + line.v2 = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); + } else if (i == 6) { + line.v3 = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); + } else if (i == 7) { + line.v4 = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); + } else if (i == 8) { + line.v5 = rSet.getObject(i) == null ? "" : (String) rSet.getObject(i); + } + } + loadPolicyLine(line, model); + } + rSet.close(); + } catch (SQLException e) { + e.printStackTrace(); + throw new Error(e); + } + } + + private CasbinRule savePolicyLine(String ptype, List rule) { + CasbinRule line = new CasbinRule(); + + line.ptype = ptype; + if (rule.size() > 0) { + line.v0 = rule.get(0); + } + if (rule.size() > 1) { + line.v1 = rule.get(1); + } + if (rule.size() > 2) { + line.v2 = rule.get(2); + } + if (rule.size() > 3) { + line.v3 = rule.get(3); + } + if (rule.size() > 4) { + line.v4 = rule.get(4); + } + if (rule.size() > 5) { + line.v5 = rule.get(5); + } + + return line; + } + + /** + * savePolicy saves all policy rules to the storage. + */ + @Override + public void savePolicy(Model model) { + String cleanSql = "delete from casbin_rule"; + String addSql = "INSERT INTO casbin_rule (ptype,v0,v1,v2,v3,v4,v5) VALUES(?,?,?,?,?,?,?)"; + + try { + conn.setAutoCommit(false); + + int count = 0; + + try (Statement statement = conn.createStatement(); PreparedStatement ps = conn.prepareStatement(addSql)) { + statement.execute(cleanSql); + for (Map.Entry entry : model.model.get("p").entrySet()) { + String ptype = entry.getKey(); + Assertion ast = entry.getValue(); + + for (List rule : ast.policy) { + CasbinRule line = savePolicyLine(ptype, rule); + + ps.setString(1, line.ptype); + ps.setString(2, line.v0); + ps.setString(3, line.v1); + ps.setString(4, line.v2); + ps.setString(5, line.v3); + ps.setString(6, line.v4); + ps.setString(7, line.v5); + + ps.addBatch(); + if (++count % batchSize == 0) { + ps.executeBatch(); + } + } + } + + for (Map.Entry entry : model.model.get("g").entrySet()) { + String ptype = entry.getKey(); + Assertion ast = entry.getValue(); + + for (List rule : ast.policy) { + CasbinRule line = savePolicyLine(ptype, rule); + + ps.setString(1, line.ptype); + ps.setString(2, line.v0); + ps.setString(3, line.v1); + ps.setString(4, line.v2); + ps.setString(5, line.v3); + ps.setString(6, line.v4); + ps.setString(7, line.v5); + + ps.addBatch(); + if (++count % batchSize == 0) { + ps.executeBatch(); + } + } + } + + ps.executeBatch(); + + conn.commit(); + } catch (SQLException e) { + conn.rollback(); + + e.printStackTrace(); + throw new Error(e); + } finally { + conn.setAutoCommit(true); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new Error(e); + } + } + + /** + * addPolicy adds a policy rule to the storage. + */ + @Override + public void addPolicy(String sec, String ptype, List rule) { + if (CollectionUtils.isEmpty(rule)) return; + + String sql = "INSERT INTO casbin_rule (ptype,v0,v1,v2,v3,v4,v5) VALUES(?,?,?,?,?,?,?)"; + + try(PreparedStatement ps = conn.prepareStatement(sql)) { + CasbinRule line = savePolicyLine(ptype, rule); + + ps.setString(1, line.ptype); + ps.setString(2, line.v0); + ps.setString(3, line.v1); + ps.setString(4, line.v2); + ps.setString(5, line.v3); + ps.setString(6, line.v4); + ps.setString(7, line.v5); + ps.addBatch(); + ps.executeBatch(); + } catch (SQLException e) { + e.printStackTrace(); + throw new Error(e); + } + } + + /** + * removePolicy removes a policy rule from the storage. + */ + @Override + public void removePolicy(String sec, String ptype, List rule) { + if (CollectionUtils.isEmpty(rule)) return; + removeFilteredPolicy(sec, ptype, 0, rule.toArray(new String[0])); + } + + /** + * removeFilteredPolicy removes policy rules that match the filter from the storage. + */ + @Override + public void removeFilteredPolicy(String sec, String ptype, int fieldIndex, String... fieldValues) { + List values = Optional.of(Arrays.asList(fieldValues)).orElse(new ArrayList<>()); + if (CollectionUtils.isEmpty(values)) return; + String sql = "DELETE FROM casbin_rule WHERE ptype = ?"; + int columnIndex = fieldIndex; + for (int i = 0; i < values.size(); i++) { + sql = String.format("%s%s%s%s", sql, " AND v", columnIndex, " = ?"); + columnIndex++; + } + + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, ptype); + for (int j = 0; j < values.size(); j++) { + ps.setString(j + 2, values.get(j)); + } + + ps.addBatch(); + + ps.executeBatch(); + } catch (SQLException e) { + e.printStackTrace(); + throw new Error(e); + } + } + + /** + * Close the Connection. + */ + public void close() throws SQLException { + conn.close(); + } +} diff --git a/src/test/java/org/casbin/adapter/JDBCAdapterTest.java b/src/test/java/org/casbin/adapter/JDBCAdapterTest.java index 031fcd2..d69ad6f 100644 --- a/src/test/java/org/casbin/adapter/JDBCAdapterTest.java +++ b/src/test/java/org/casbin/adapter/JDBCAdapterTest.java @@ -14,12 +14,18 @@ package org.casbin.adapter; +import org.casbin.jcasbin.exception.CasbinAdapterException; +import org.casbin.jcasbin.main.Enforcer; +import org.casbin.jcasbin.persist.file_adapter.FilteredAdapter; +import org.junit.Assert; import org.junit.Test; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import static java.util.Arrays.asList; +import static org.casbin.adapter.JDBCAdapterTestSets.*; import static org.junit.Assert.fail; public class JDBCAdapterTest { @@ -101,4 +107,99 @@ public void testSQLServerAdapter() { } }); } + + @Test + public void testLoadFilteredPolicyEmptyFilter() throws Exception { + JDBCAdapter adapter = new MySQLAdapterCreator().create(); + + // Because the DB is empty at first, + // so we need to load the policy from the file adapter (.CSV) first. + Enforcer e = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); + + // This is a trick to save the current policy to the DB. + // We can't call e.savePolicy() because the adapter in the enforcer is still the file adapter. + // The current policy means the policy in the jCasbin enforcer (aka in memory). + adapter.savePolicy(e.getModel()); + + // Clear the current policy. + e.clearPolicy(); + testGetPolicy(e, asList()); + + // Load the policy from DB. + adapter.loadFilteredPolicy(e.getModel(), null); + testGetPolicy(e, asList( + asList("alice", "data1", "read"), + asList("bob", "data2", "write"), + asList("data2_admin", "data2", "read"), + asList("data2_admin", "data2", "write"))); + + // Note: you don't need to look at the above code + // if you already have a working DB with policy inside. + e = new Enforcer("examples/rbac_model.conf", adapter); + testGetPolicy(e, asList( + asList("alice", "data1", "read"), + asList("bob", "data2", "write"), + asList("data2_admin", "data2", "read"), + asList("data2_admin", "data2", "write"))); + + adapter.close(); + } + + @Test + public void testLoadFilteredPolicyInvalidFilter() throws Exception { + JDBCAdapter adapter = new MySQLAdapterCreator().create(); + // Because the DB is empty at first, + // so we need to load the policy from the file adapter (.CSV) first. + Enforcer e = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); + + // This is a trick to save the current policy to the DB. + // We can't call e.savePolicy() because the adapter in the enforcer is still the file adapter. + // The current policy means the policy in the jCasbin enforcer (aka in memory). + adapter.savePolicy(e.getModel()); + + // Clear the current policy. + e.clearPolicy(); + testGetPolicy(e, asList()); + + // Load the policy from DB. + Assert.assertThrows(CasbinAdapterException.class, () -> adapter.loadFilteredPolicy(e.getModel(), new Object())); + + adapter.close(); + } + + @Test + public void testLoadFilteredPolicy() throws Exception { + JDBCAdapter adapter = new MySQLAdapterCreator().create(); + + FilteredAdapter.Filter f = new FilteredAdapter.Filter(); + f.g = new String[]{ + "", "", "domain1" + }; + f.p = new String[]{ + "", "domain1" + }; + + // Because the DB is empty at first, + // so we need to load the policy from the file adapter (.CSV) first. + Enforcer e = new Enforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv"); + // This is a trick to save the current policy to the DB. + // We can't call e.savePolicy() because the adapter in the enforcer is still the file adapter. + // The current policy means the policy in the jCasbin enforcer (aka in memory). + adapter.savePolicy(e.getModel()); + + testHasPolicy(e, asList("admin", "domain1", "data1", "read"), true); + testHasPolicy(e, asList("admin", "domain2", "data2", "read"), true); + + // Clear the current policy. + e.clearPolicy(); + testGetPolicy(e, asList()); + + // Load the policy from DB. + adapter.loadFilteredPolicy(e.getModel(), f); + + testHasPolicy(e, asList("admin", "domain1", "data1", "read"), true); + testHasPolicy(e, asList("admin", "domain2", "data2", "read"), false); + + adapter.close(); + } } diff --git a/src/test/java/org/casbin/adapter/JDBCAdapterTestSets.java b/src/test/java/org/casbin/adapter/JDBCAdapterTestSets.java index c1440dc..688baed 100644 --- a/src/test/java/org/casbin/adapter/JDBCAdapterTestSets.java +++ b/src/test/java/org/casbin/adapter/JDBCAdapterTestSets.java @@ -15,7 +15,6 @@ package org.casbin.adapter; import org.casbin.jcasbin.main.Enforcer; -import org.casbin.jcasbin.model.Model; import org.casbin.jcasbin.util.Util; import java.util.List; @@ -30,7 +29,7 @@ private static void testEnforce(Enforcer e, String sub, Object obj, String act, assertEquals(res, e.enforce(sub, obj, act)); } - private static void testGetPolicy(Enforcer e, List> res) { + public static void testGetPolicy(Enforcer e, List> res) { List> myRes = e.getPolicy(); Util.logPrint("Policy: " + myRes); @@ -39,6 +38,15 @@ private static void testGetPolicy(Enforcer e, List> res) { } } + public static void testHasPolicy(Enforcer e, List policy, boolean res) { + boolean myRes = e.hasPolicy(policy); + Util.logPrint("Has policy " + Util.arrayToString(policy) + ": " + myRes); + + if (res != myRes) { + fail("Has policy " + Util.arrayToString(policy) + ": " + myRes + ", supposed to be " + res); + } + } + static void testAdapter(JDBCAdapter a) { // Because the DB is empty at first, // so we need to load the policy from the file adapter (.CSV) first.