Forráskód Böngészése

Allow use of non-numeric (e.g. UUID) values for ObjectIdentity.getIdentifier()

Prior to this commit, the ObjectIdentity id had to be a number. This
commit allows for domain objects to use UUIDs as their identifier. The
fully qualified class name of the identifier type can be specified
in the acl_object_identity table and a ConversionService can be provided
to BasicLookupStrategy to convert from String to the actual identifier
type.

There are the following other changes:

 - BasicLookupStrategy has a new property, aclClassIdSupported, which
 is used to retrieve the new column from the database. This preserves
 backwards-compatibility, as it is false by default.

 - JdbcMutableAclService has the same property, aclClassIdSupported,
 which is needed to modify the insert statement to write to the
 new column. Defaults to false for backwards-compatibility.

 - Tests have been updated to verify both the existing functionality
 for backwards-compatibility and the new functionality.

Fixes gh-1224
Paul Wheeler 8 éve
szülő
commit
6decf1c8ef
22 módosított fájl, 1200 hozzáadás és 368 törlés
  1. 125 0
      acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java
  2. 35 9
      acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java
  3. 36 4
      acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java
  4. 24 5
      acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java
  5. 1 1
      acl/src/main/resources/createAclSchemaMySQL.sql
  6. 1 1
      acl/src/main/resources/createAclSchemaOracle.sql
  7. 2 1
      acl/src/main/resources/createAclSchemaPostgres.sql
  8. 1 1
      acl/src/main/resources/createAclSchemaSqlServer.sql
  9. 47 0
      acl/src/main/resources/createAclSchemaWithAclClassIdType.sql
  10. 36 0
      acl/src/test/java/org/springframework/security/acls/TargetObjectWithUUID.java
  11. 341 0
      acl/src/test/java/org/springframework/security/acls/jdbc/AbstractBasicLookupStrategyTests.java
  12. 160 0
      acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTest.java
  13. 16 307
      acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java
  14. 72 0
      acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTestsDbHelper.java
  15. 117 0
      acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyWithAclClassTypeTests.java
  16. 57 30
      acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java
  17. 83 0
      acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTestsWithAclClassId.java
  18. 4 1
      acl/src/test/resources/jdbcMutableAclServiceTests-context.xml
  19. 33 0
      acl/src/test/resources/jdbcMutableAclServiceTestsWithAclClass-context.xml
  20. 5 5
      docs/manual/src/docs/asciidoc/index.adoc
  21. 2 1
      samples/xml/contacts/src/main/java/sample/contact/DataSourcePopulator.java
  22. 2 2
      samples/xml/dms/src/main/java/sample/dms/DataSourcePopulator.java

+ 125 - 0
acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java

@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * 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.springframework.security.acls.jdbc;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.security.acls.model.ObjectIdentity;
+
+/**
+ * Utility class for helping convert database representations of {@link ObjectIdentity#getIdentifier()} into
+ * the correct Java type as specified by <code>acl_class.class_id_type</code>.
+ * @author paulwheeler
+ */
+class AclClassIdUtils {
+	private static final String DEFAULT_CLASS_ID_TYPE_COLUMN_NAME = "class_id_type";
+	private static final Log log = LogFactory.getLog(AclClassIdUtils.class);
+
+	private ConversionService conversionService;
+
+	public AclClassIdUtils() {
+	}
+
+	/**
+	 * Converts the raw type from the database into the right Java type. For most applications the 'raw type' will be Long, for some applications
+	 * it could be String.
+	 * @param identifier The identifier from the database
+	 * @param resultSet  Result set of the query
+	 * @return The identifier in the appropriate target Java type. Typically Long or UUID.
+	 * @throws SQLException
+	 */
+	Serializable identifierFrom(Serializable identifier, ResultSet resultSet) throws SQLException {
+		if (isString(identifier) && hasValidClassIdType(resultSet)
+			&& canConvertFromStringTo(classIdTypeFrom(resultSet))) {
+
+			identifier = convertFromStringTo((String) identifier, classIdTypeFrom(resultSet));
+		} else {
+			// Assume it should be a Long type
+			identifier = convertToLong(identifier);
+		}
+
+		return identifier;
+	}
+
+	private boolean hasValidClassIdType(ResultSet resultSet) throws SQLException {
+		boolean hasClassIdType = false;
+		try {
+			hasClassIdType = classIdTypeFrom(resultSet) != null;
+		} catch (SQLException e) {
+			log.debug("Unable to obtain the class id type", e);
+		}
+		return hasClassIdType;
+	}
+
+	private <T  extends Serializable> Class<T> classIdTypeFrom(ResultSet resultSet) throws SQLException {
+		return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME));
+	}
+
+	private <T extends Serializable> Class<T> classIdTypeFrom(String className) {
+		Class targetType = null;
+		if (className != null) {
+			try {
+				targetType = Class.forName(className);
+			} catch (ClassNotFoundException e) {
+				log.debug("Unable to find class id type on classpath", e);
+			}
+		}
+		return targetType;
+	}
+
+	private <T> boolean canConvertFromStringTo(Class<T> targetType) {
+		return hasConversionService() && conversionService.canConvert(String.class, targetType);
+	}
+
+	private <T extends Serializable> T convertFromStringTo(String identifier, Class<T> targetType) {
+		return conversionService.convert(identifier, targetType);
+	}
+
+	private boolean hasConversionService() {
+		return conversionService != null;
+	}
+
+	/**
+	 * Converts to a {@link Long}, attempting to use the {@link ConversionService} if available.
+	 * @param identifier    The identifier
+	 * @return Long version of the identifier
+	 * @throws NumberFormatException if the string cannot be parsed to a long.
+	 * @throws org.springframework.core.convert.ConversionException if a conversion exception occurred
+	 * @throws IllegalArgumentException if targetType is null
+	 */
+	private Long convertToLong(Serializable identifier) {
+		Long idAsLong;
+		if (hasConversionService()) {
+			idAsLong = conversionService.convert(identifier, Long.class);
+		} else {
+			idAsLong = Long.valueOf(identifier.toString());
+		}
+		return idAsLong;
+	}
+
+	private boolean isString(Serializable object) {
+		return object.getClass().isAssignableFrom(String.class);
+	}
+
+	public void setConversionService(ConversionService conversionService) {
+		this.conversionService = conversionService;
+	}
+}

+ 35 - 9
acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,6 +30,9 @@ import java.util.Set;
 
 import javax.sql.DataSource;
 
+import org.springframework.core.convert.ConversionException;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementSetter;
 import org.springframework.jdbc.core.ResultSetExtractor;
@@ -78,7 +81,7 @@ import org.springframework.util.Assert;
  */
 public class BasicLookupStrategy implements LookupStrategy {
 
-	public final static String DEFAULT_SELECT_CLAUSE = "select acl_object_identity.object_id_identity, "
+	private final static String DEFAULT_SELECT_CLAUSE_COLUMNS = "select acl_object_identity.object_id_identity, "
 			+ "acl_entry.ace_order,  "
 			+ "acl_object_identity.id as acl_id, "
 			+ "acl_object_identity.parent_object, "
@@ -92,13 +95,19 @@ public class BasicLookupStrategy implements LookupStrategy {
 			+ "acl_sid.sid as ace_sid,  "
 			+ "acli_sid.principal as acl_principal, "
 			+ "acli_sid.sid as acl_sid, "
-			+ "acl_class.class "
-			+ "from acl_object_identity "
+			+ "acl_class.class ";
+	private final static String DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN = ", acl_class.class_id_type  ";
+	private final static String DEFAULT_SELECT_CLAUSE_FROM = "from acl_object_identity "
 			+ "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid "
 			+ "left join acl_class on acl_class.id = acl_object_identity.object_id_class   "
 			+ "left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity "
 			+ "left join acl_sid on acl_entry.sid = acl_sid.id  " + "where ( ";
 
+	public final static String DEFAULT_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS + DEFAULT_SELECT_CLAUSE_FROM;
+
+	public final static String DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS +
+		DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN + DEFAULT_SELECT_CLAUSE_FROM;
+
 	private final static String DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE = "(acl_object_identity.id = ?)";
 
 	private final static String DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE = "(acl_object_identity.object_id_identity = ? and acl_class.class = ?)";
@@ -126,6 +135,8 @@ public class BasicLookupStrategy implements LookupStrategy {
 	private String lookupObjectIdentitiesWhereClause = DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE;
 	private String orderByClause = DEFAULT_ORDER_BY_CLAUSE;
 
+	private AclClassIdUtils aclClassIdUtils;
+
 	// ~ Constructors
 	// ===================================================================================================
 
@@ -161,9 +172,9 @@ public class BasicLookupStrategy implements LookupStrategy {
 		this.aclCache = aclCache;
 		this.aclAuthorizationStrategy = aclAuthorizationStrategy;
 		this.grantingStrategy = grantingStrategy;
+		this.aclClassIdUtils = new AclClassIdUtils();
 		fieldAces.setAccessible(true);
 		fieldAcl.setAccessible(true);
-
 	}
 
 	// ~ Methods
@@ -383,10 +394,9 @@ public class BasicLookupStrategy implements LookupStrategy {
 							// No need to check for nulls, as guaranteed non-null by
 							// ObjectIdentity.getIdentifier() interface contract
 							String identifier = oid.getIdentifier().toString();
-							long id = (Long.valueOf(identifier)).longValue();
 
 							// Inject values
-							ps.setLong((2 * i) + 1, id);
+							ps.setString((2 * i) + 1, identifier);
 							ps.setString((2 * i) + 2, type);
 							i++;
 						}
@@ -537,6 +547,18 @@ public class BasicLookupStrategy implements LookupStrategy {
 		this.orderByClause = orderByClause;
 	}
 
+	public final void setAclClassIdSupported(boolean aclClassIdSupported) {
+		if (aclClassIdSupported) {
+			Assert.isTrue(this.selectClause.equals(DEFAULT_SELECT_CLAUSE), "Cannot set aclClassIdSupported and override the select clause; "
+				+ "just override the select clause");
+			this.selectClause = DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE;
+		}
+	}
+
+	public final void setAclClassIdUtils(AclClassIdUtils aclClassIdUtils) {
+		this.aclClassIdUtils = aclClassIdUtils;
+	}
+
 	// ~ Inner Classes
 	// ==================================================================================================
 
@@ -602,6 +624,7 @@ public class BasicLookupStrategy implements LookupStrategy {
 		 * @param rs the ResultSet focused on a current row
 		 *
 		 * @throws SQLException if something goes wrong converting values
+		 * @throws ConversionException if can't convert to the desired Java type
 		 */
 		private void convertCurrentResultIntoObject(Map<Serializable, Acl> acls,
 				ResultSet rs) throws SQLException {
@@ -612,9 +635,12 @@ public class BasicLookupStrategy implements LookupStrategy {
 
 			if (acl == null) {
 				// Make an AclImpl and pop it into the Map
+
+				// If the Java type is a String, check to see if we can convert it to the target id type, e.g. UUID.
+				Serializable identifier = (Serializable) rs.getObject("object_id_identity");
+				identifier = aclClassIdUtils.identifierFrom(identifier, rs);
 				ObjectIdentity objectIdentity = new ObjectIdentityImpl(
-						rs.getString("class"), Long.valueOf(rs
-								.getLong("object_id_identity")));
+					rs.getString("class"), identifier);
 
 				Acl parentAcl = null;
 				long parentAclId = rs.getLong("parent_object");

+ 36 - 4
acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
  */
 package org.springframework.security.acls.jdbc;
 
+import java.io.Serializable;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Arrays;
@@ -49,8 +50,15 @@ public class JdbcAclService implements AclService {
 	// =====================================================================================
 
 	protected static final Log log = LogFactory.getLog(JdbcAclService.class);
-	private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, class.class as class "
-			+ "from acl_object_identity obj, acl_object_identity parent, acl_class class "
+	private static final String DEFAULT_SELECT_ACL_CLASS_COLUMNS = "class.class as class";
+	private static final String DEFAULT_SELECT_ACL_CLASS_COLUMNS_WITH_ID_TYPE = DEFAULT_SELECT_ACL_CLASS_COLUMNS + ", class.class_id_type as class_id_type";
+	private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, " + DEFAULT_SELECT_ACL_CLASS_COLUMNS
+			+ " from acl_object_identity obj, acl_object_identity parent, acl_class class "
+			+ "where obj.parent_object = parent.id and obj.object_id_class = class.id "
+			+ "and parent.object_id_identity = ? and parent.object_id_class = ("
+			+ "select id FROM acl_class where acl_class.class = ?)";
+	private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE = "select obj.object_id_identity as obj_id, " + DEFAULT_SELECT_ACL_CLASS_COLUMNS_WITH_ID_TYPE
+			+ " from acl_object_identity obj, acl_object_identity parent, acl_class class "
 			+ "where obj.parent_object = parent.id and obj.object_id_class = class.id "
 			+ "and parent.object_id_identity = ? and parent.object_id_class = ("
 			+ "select id FROM acl_class where acl_class.class = ?)";
@@ -60,7 +68,9 @@ public class JdbcAclService implements AclService {
 
 	protected final JdbcTemplate jdbcTemplate;
 	private final LookupStrategy lookupStrategy;
+	private boolean aclClassIdSupported;
 	private String findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL;
+	private AclClassIdUtils aclClassIdUtils;
 
 	// ~ Constructors
 	// ===================================================================================================
@@ -70,6 +80,7 @@ public class JdbcAclService implements AclService {
 		Assert.notNull(lookupStrategy, "LookupStrategy required");
 		this.jdbcTemplate = new JdbcTemplate(dataSource);
 		this.lookupStrategy = lookupStrategy;
+		this.aclClassIdUtils = new AclClassIdUtils();
 	}
 
 	// ~ Methods
@@ -82,7 +93,8 @@ public class JdbcAclService implements AclService {
 					public ObjectIdentity mapRow(ResultSet rs, int rowNum)
 							throws SQLException {
 						String javaType = rs.getString("class");
-						Long identifier = new Long(rs.getLong("obj_id"));
+						Serializable identifier = (Serializable) rs.getObject("obj_id");
+						identifier = aclClassIdUtils.identifierFrom(identifier, rs);
 
 						return new ObjectIdentityImpl(javaType, identifier);
 					}
@@ -138,4 +150,24 @@ public class JdbcAclService implements AclService {
 	public void setFindChildrenQuery(String findChildrenSql) {
 		this.findChildrenSql = findChildrenSql;
 	}
+
+	public void setAclClassIdSupported(boolean aclClassIdSupported) {
+		this.aclClassIdSupported = aclClassIdSupported;
+		if (aclClassIdSupported) {
+			// Change the default insert if it hasn't been overridden
+			if (this.findChildrenSql.equals(DEFAULT_SELECT_ACL_WITH_PARENT_SQL)) {
+				this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE;
+			} else {
+				log.debug("Find children statement has already been overridden, so not overridding the default");
+			}
+		}
+	}
+
+	public void setAclClassIdUtils(AclClassIdUtils aclClassIdUtils) {
+		this.aclClassIdUtils = aclClassIdUtils;
+	}
+
+	protected boolean isAclClassIdSupported() {
+		return aclClassIdSupported;
+	}
 }

+ 24 - 5
acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -58,6 +58,8 @@ import org.springframework.util.Assert;
  * @author Johannes Zlattinger
  */
 public class JdbcMutableAclService extends JdbcAclService implements MutableAclService {
+	private static final String DEFAULT_INSERT_INTO_ACL_CLASS = "insert into acl_class (class) values (?)";
+	private static final String DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID = "insert into acl_class (class, class_id_type) values (?, ?)";
 	// ~ Instance fields
 	// ================================================================================================
 
@@ -67,7 +69,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
 	private String deleteObjectIdentityByPrimaryKey = "delete from acl_object_identity where id=?";
 	private String classIdentityQuery = "call identity()";
 	private String sidIdentityQuery = "call identity()";
-	private String insertClass = "insert into acl_class (class) values (?)";
+	private String insertClass = DEFAULT_INSERT_INTO_ACL_CLASS;
 	private String insertEntry = "insert into acl_entry "
 			+ "(acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)"
 			+ "values (?, ?, ?, ?, ?, ?, ?)";
@@ -167,7 +169,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
 	 */
 	protected void createObjectIdentity(ObjectIdentity object, Sid owner) {
 		Long sidId = createOrRetrieveSidPrimaryKey(owner, true);
-		Long classId = createOrRetrieveClassPrimaryKey(object.getType(), true);
+		Long classId = createOrRetrieveClassPrimaryKey(object.getType(), true, object.getIdentifier().getClass());
 		jdbcTemplate.update(insertObjectIdentity, classId, object.getIdentifier(), sidId,
 				Boolean.TRUE);
 	}
@@ -181,7 +183,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
 	 *
 	 * @return the primary key or null if not found
 	 */
-	protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate) {
+	protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) {
 		List<Long> classIds = jdbcTemplate.queryForList(selectClassPrimaryKey,
 				new Object[] { type }, Long.class);
 
@@ -190,7 +192,11 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
 		}
 
 		if (allowCreate) {
-			jdbcTemplate.update(insertClass, type);
+			if (!isAclClassIdSupported()) {
+				jdbcTemplate.update(insertClass, type);
+			} else {
+				jdbcTemplate.update(insertClass, type, idType.getCanonicalName());
+			}
 			Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(),
 					"Transaction must be running");
 			return jdbcTemplate.queryForObject(classIdentityQuery, Long.class);
@@ -485,4 +491,17 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
 	public void setForeignKeysInDatabase(boolean foreignKeysInDatabase) {
 		this.foreignKeysInDatabase = foreignKeysInDatabase;
 	}
+
+	@Override
+	public void setAclClassIdSupported(boolean aclClassIdSupported) {
+		super.setAclClassIdSupported(aclClassIdSupported);
+		if (aclClassIdSupported) {
+			// Change the default insert if it hasn't been overridden
+			if (this.insertClass.equals(DEFAULT_INSERT_INTO_ACL_CLASS)) {
+				this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID;
+			} else {
+				log.debug("Insert class statement has already been overridden, so not overridding the default");
+			}
+		}
+	}
 }

+ 1 - 1
acl/src/main/resources/createAclSchemaMySQL.sql

@@ -21,7 +21,7 @@ CREATE TABLE acl_class (
 CREATE TABLE acl_object_identity (
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
     object_id_class BIGINT UNSIGNED NOT NULL,
-    object_id_identity BIGINT NOT NULL,
+    object_id_identity VARCHAR(36) NOT NULL,
     parent_object BIGINT UNSIGNED,
     owner_sid BIGINT UNSIGNED,
     entries_inheriting BOOLEAN NOT NULL,

+ 1 - 1
acl/src/main/resources/createAclSchemaOracle.sql

@@ -43,7 +43,7 @@ END;
 CREATE TABLE acl_object_identity (
     id NUMBER(38) NOT NULL PRIMARY KEY,
     object_id_class NUMBER(38) NOT NULL,
-    object_id_identity NUMBER(38) NOT NULL,
+    object_id_identity NVARCHAR2(36) NOT NULL,
     parent_object NUMBER(38),
     owner_sid NUMBER(38),
     entries_inheriting NUMBER(1) NOT NULL CHECK (entries_inheriting in (0, 1)),

+ 2 - 1
acl/src/main/resources/createAclSchemaPostgres.sql

@@ -15,13 +15,14 @@ create table acl_sid(
 create table acl_class(
     id bigserial not null primary key,
     class varchar(100) not null,
+    class_id_type varchar(100),
     constraint unique_uk_2 unique(class)
 );
 
 create table acl_object_identity(
     id bigserial primary key,
     object_id_class bigint not null,
-    object_id_identity bigint not null,
+    object_id_identity varchar(36) not null,
     parent_object bigint,
     owner_sid bigint,
     entries_inheriting boolean not null,

+ 1 - 1
acl/src/main/resources/createAclSchemaSqlServer.sql

@@ -21,7 +21,7 @@ CREATE TABLE acl_class (
 CREATE TABLE acl_object_identity (
     id BIGINT NOT NULL IDENTITY PRIMARY KEY,
     object_id_class BIGINT NOT NULL,
-    object_id_identity BIGINT NOT NULL,
+    object_id_identity VARCHAR(36) NOT NULL,
     parent_object BIGINT,
     owner_sid BIGINT,
     entries_inheriting BIT NOT NULL,

+ 47 - 0
acl/src/main/resources/createAclSchemaWithAclClassIdType.sql

@@ -0,0 +1,47 @@
+-- ACL schema sql used in HSQLDB
+
+-- drop table acl_entry;
+-- drop table acl_object_identity;
+-- drop table acl_class;
+-- drop table acl_sid;
+
+create table acl_sid(
+    id bigint generated by default as identity(start with 100) not null primary key,
+    principal boolean not null,
+    sid varchar_ignorecase(100) not null,
+    constraint unique_uk_1 unique(sid,principal)
+);
+
+create table acl_class(
+    id bigint generated by default as identity(start with 100) not null primary key,
+    class varchar_ignorecase(100) not null,
+    class_id_type varchar_ignorecase(100),
+    constraint unique_uk_2 unique(class)
+);
+
+create table acl_object_identity(
+    id bigint generated by default as identity(start with 100) not null primary key,
+    object_id_class bigint not null,
+    object_id_identity varchar_ignorecase(36) not null,
+    parent_object bigint,
+    owner_sid bigint,
+    entries_inheriting boolean not null,
+    constraint unique_uk_3 unique(object_id_class,object_id_identity),
+    constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id),
+    constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id),
+    constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id)
+);
+
+create table acl_entry(
+    id bigint generated by default as identity(start with 100) not null primary key,
+    acl_object_identity bigint not null,
+    ace_order int not null,
+    sid bigint not null,
+    mask integer not null,
+    granting boolean not null,
+    audit_success boolean not null,
+    audit_failure boolean not null,
+    constraint unique_uk_4 unique(acl_object_identity,ace_order),
+    constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id),
+    constraint foreign_fk_5 foreign key(sid) references acl_sid(id)
+);

+ 36 - 0
acl/src/test/java/org/springframework/security/acls/TargetObjectWithUUID.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * 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.springframework.security.acls;
+
+import java.util.UUID;
+
+/**
+ * Dummy domain object class with a {@link UUID} for the Id.
+ *
+ * @author Luke Taylor
+ */
+public final class TargetObjectWithUUID {
+
+	private UUID id;
+
+	public UUID getId() {
+		return id;
+	}
+
+	public void setId(UUID id) {
+		this.id = id;
+	}
+}

+ 341 - 0
acl/src/test/java/org/springframework/security/acls/jdbc/AbstractBasicLookupStrategyTests.java

@@ -0,0 +1,341 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * 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.springframework.security.acls.jdbc;
+
+import static org.assertj.core.api.Assertions.*;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Ehcache;
+
+import org.junit.*;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.acls.TargetObject;
+import org.springframework.security.acls.TargetObjectWithUUID;
+import org.springframework.security.acls.domain.*;
+import org.springframework.security.acls.model.Acl;
+import org.springframework.security.acls.model.AuditableAccessControlEntry;
+import org.springframework.security.acls.model.MutableAcl;
+import org.springframework.security.acls.model.NotFoundException;
+import org.springframework.security.acls.model.ObjectIdentity;
+import org.springframework.security.acls.model.Permission;
+import org.springframework.security.acls.model.Sid;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.*;
+
+import javax.sql.DataSource;
+
+/**
+ * Tests {@link BasicLookupStrategy}
+ *
+ * @author Andrei Stefan
+ */
+public abstract class AbstractBasicLookupStrategyTests {
+
+	protected static final Sid BEN_SID = new PrincipalSid("ben");
+	protected static final String TARGET_CLASS = TargetObject.class.getName();
+	protected static final String TARGET_CLASS_WITH_UUID = TargetObjectWithUUID.class.getName();
+	protected static final UUID OBJECT_IDENTITY_UUID = UUID.randomUUID();
+	protected static final Long OBJECT_IDENTITY_LONG_AS_UUID = 110L;
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private BasicLookupStrategy strategy;
+	private static CacheManager cacheManager;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public abstract JdbcTemplate getJdbcTemplate();
+
+	public abstract DataSource getDataSource();
+
+	@BeforeClass
+	public static void initCacheManaer() {
+		cacheManager = CacheManager.create();
+		cacheManager.addCache(new Cache("basiclookuptestcache", 500, false, false, 30, 30));
+	}
+
+	@AfterClass
+	public static void shutdownCacheManager() {
+		cacheManager.removalAll();
+		cacheManager.shutdown();
+	}
+
+	@Before
+	public void populateDatabase() {
+		String query = "INSERT INTO acl_sid(ID,PRINCIPAL,SID) VALUES (1,1,'ben');"
+			+ "INSERT INTO acl_class(ID,CLASS) VALUES (2,'" + TARGET_CLASS + "');"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (1,2,100,null,1,1);"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (2,2,101,1,1,1);"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (3,2,102,2,1,1);"
+			+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (1,1,0,1,1,1,0,0);"
+			+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (2,1,1,1,2,0,0,0);"
+			+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (3,2,0,1,8,1,0,0);"
+			+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (4,3,0,1,8,0,0,0);";
+		getJdbcTemplate().execute(query);
+	}
+
+	@Before
+	public void initializeBeans() {
+		strategy = new BasicLookupStrategy(getDataSource(), aclCache(), aclAuthStrategy(),
+			new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()));
+		strategy.setPermissionFactory(new DefaultPermissionFactory());
+	}
+
+	protected AclAuthorizationStrategy aclAuthStrategy() {
+		return new AclAuthorizationStrategyImpl(
+			new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
+	}
+
+	protected EhCacheBasedAclCache aclCache() {
+		return new EhCacheBasedAclCache(getCache(),
+			new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()),
+			new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
+	}
+
+
+	@After
+	public void emptyDatabase() {
+		String query = "DELETE FROM acl_entry;" + "DELETE FROM acl_object_identity WHERE ID = 9;"
+			+ "DELETE FROM acl_object_identity WHERE ID = 8;" + "DELETE FROM acl_object_identity WHERE ID = 7;"
+			+ "DELETE FROM acl_object_identity WHERE ID = 6;" + "DELETE FROM acl_object_identity WHERE ID = 5;"
+			+ "DELETE FROM acl_object_identity WHERE ID = 4;" + "DELETE FROM acl_object_identity WHERE ID = 3;"
+			+ "DELETE FROM acl_object_identity WHERE ID = 2;" + "DELETE FROM acl_object_identity WHERE ID = 1;"
+			+ "DELETE FROM acl_class;" + "DELETE FROM acl_sid;";
+		getJdbcTemplate().execute(query);
+	}
+
+	protected Ehcache getCache() {
+		Ehcache cache = cacheManager.getCache("basiclookuptestcache");
+		cache.removeAll();
+		return cache;
+	}
+
+	@Test
+	public void testAclsRetrievalWithDefaultBatchSize() throws Exception {
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(101));
+		// Deliberately use an integer for the child, to reproduce bug report in SEC-819
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(102));
+
+		Map<ObjectIdentity, Acl> map = this.strategy
+			.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
+		checkEntries(topParentOid, middleParentOid, childOid, map);
+	}
+
+	@Test
+	public void testAclsRetrievalFromCacheOnly() throws Exception {
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(101));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
+
+		// Objects were put in cache
+		strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
+
+		// Let's empty the database to force acls retrieval from cache
+		emptyDatabase();
+		Map<ObjectIdentity, Acl> map = this.strategy
+			.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
+
+		checkEntries(topParentOid, middleParentOid, childOid, map);
+	}
+
+	@Test
+	public void testAclsRetrievalWithCustomBatchSize() throws Exception {
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(101));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
+
+		// Set a batch size to allow multiple database queries in order to retrieve all
+		// acls
+		this.strategy.setBatchSize(1);
+		Map<ObjectIdentity, Acl> map = this.strategy
+			.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
+		checkEntries(topParentOid, middleParentOid, childOid, map);
+	}
+
+	private void checkEntries(ObjectIdentity topParentOid, ObjectIdentity middleParentOid, ObjectIdentity childOid,
+		Map<ObjectIdentity, Acl> map) throws Exception {
+		assertThat(map).hasSize(3);
+
+		MutableAcl topParent = (MutableAcl) map.get(topParentOid);
+		MutableAcl middleParent = (MutableAcl) map.get(middleParentOid);
+		MutableAcl child = (MutableAcl) map.get(childOid);
+
+		// Check the retrieved versions has IDs
+		assertThat(topParent.getId()).isNotNull();
+		assertThat(middleParent.getId()).isNotNull();
+		assertThat(child.getId()).isNotNull();
+
+		// Check their parents were correctly retrieved
+		assertThat(topParent.getParentAcl()).isNull();
+		assertThat(middleParent.getParentAcl().getObjectIdentity()).isEqualTo(topParentOid);
+		assertThat(child.getParentAcl().getObjectIdentity()).isEqualTo(middleParentOid);
+
+		// Check their ACEs were correctly retrieved
+		assertThat(topParent.getEntries()).hasSize(2);
+		assertThat(middleParent.getEntries()).hasSize(1);
+		assertThat(child.getEntries()).hasSize(1);
+
+		// Check object identities were correctly retrieved
+		assertThat(topParent.getObjectIdentity()).isEqualTo(topParentOid);
+		assertThat(middleParent.getObjectIdentity()).isEqualTo(middleParentOid);
+		assertThat(child.getObjectIdentity()).isEqualTo(childOid);
+
+		// Check each entry
+		assertThat(topParent.isEntriesInheriting()).isTrue();
+		assertThat(Long.valueOf(1)).isEqualTo(topParent.getId());
+		assertThat(new PrincipalSid("ben")).isEqualTo(topParent.getOwner());
+		assertThat(Long.valueOf(1)).isEqualTo(topParent.getEntries().get(0).getId());
+		assertThat(topParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.READ);
+		assertThat(topParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben"));
+		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditFailure()).isFalse();
+		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditSuccess()).isFalse();
+		assertThat((topParent.getEntries().get(0)).isGranting()).isTrue();
+
+		assertThat(Long.valueOf(2)).isEqualTo(topParent.getEntries().get(1).getId());
+		assertThat(topParent.getEntries().get(1).getPermission()).isEqualTo(BasePermission.WRITE);
+		assertThat(topParent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("ben"));
+		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditFailure()).isFalse();
+		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditSuccess()).isFalse();
+		assertThat(topParent.getEntries().get(1).isGranting()).isFalse();
+
+		assertThat(middleParent.isEntriesInheriting()).isTrue();
+		assertThat(Long.valueOf(2)).isEqualTo(middleParent.getId());
+		assertThat(new PrincipalSid("ben")).isEqualTo(middleParent.getOwner());
+		assertThat(Long.valueOf(3)).isEqualTo(middleParent.getEntries().get(0).getId());
+		assertThat(middleParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE);
+		assertThat(middleParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben"));
+		assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditFailure()).isFalse();
+		assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditSuccess()).isFalse();
+		assertThat(middleParent.getEntries().get(0).isGranting()).isTrue();
+
+		assertThat(child.isEntriesInheriting()).isTrue();
+		assertThat(Long.valueOf(3)).isEqualTo(child.getId());
+		assertThat(new PrincipalSid("ben")).isEqualTo(child.getOwner());
+		assertThat(Long.valueOf(4)).isEqualTo(child.getEntries().get(0).getId());
+		assertThat(child.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE);
+		assertThat(new PrincipalSid("ben")).isEqualTo(child.getEntries().get(0).getSid());
+		assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditFailure()).isFalse();
+		assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditSuccess()).isFalse();
+		assertThat((child.getEntries().get(0)).isGranting()).isFalse();
+	}
+
+	@Test
+	public void testAllParentsAreRetrievedWhenChildIsLoaded() throws Exception {
+		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,103,1,1,1);";
+		getJdbcTemplate().execute(query);
+
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102));
+		ObjectIdentity middleParent2Oid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(103));
+
+		// Retrieve the child
+		Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(Arrays.asList(childOid), null);
+
+		// Check that the child and all its parents were retrieved
+		assertThat(map.get(childOid)).isNotNull();
+		assertThat(map.get(childOid).getObjectIdentity()).isEqualTo(childOid);
+		assertThat(map.get(middleParentOid)).isNotNull();
+		assertThat(map.get(middleParentOid).getObjectIdentity()).isEqualTo(middleParentOid);
+		assertThat(map.get(topParentOid)).isNotNull();
+		assertThat(map.get(topParentOid).getObjectIdentity()).isEqualTo(topParentOid);
+
+		// The second parent shouldn't have been retrieved
+		assertThat(map.get(middleParent2Oid)).isNull();
+	}
+
+	/**
+	 * Test created from SEC-590.
+	 */
+	@Test
+	public void testReadAllObjectIdentitiesWhenLastElementIsAlreadyCached() throws Exception {
+		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,105,null,1,1);"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (7,2,106,6,1,1);"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (8,2,107,6,1,1);"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (9,2,108,7,1,1);"
+			+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (7,6,0,1,1,1,0,0)";
+		getJdbcTemplate().execute(query);
+
+		ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(104));
+		ObjectIdentity parent1Oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(105));
+		ObjectIdentity parent2Oid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(106));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(107));
+
+		// First lookup only child, thus populating the cache with grandParent,
+		// parent1
+		// and child
+		List<Permission> checkPermission = Arrays.asList(BasePermission.READ);
+		List<Sid> sids = Arrays.asList(BEN_SID);
+		List<ObjectIdentity> childOids = Arrays.asList(childOid);
+
+		strategy.setBatchSize(6);
+		Map<ObjectIdentity, Acl> foundAcls = strategy.readAclsById(childOids, sids);
+
+		Acl foundChildAcl = foundAcls.get(childOid);
+		assertThat(foundChildAcl).isNotNull();
+		assertThat(foundChildAcl.isGranted(checkPermission, sids, false)).isTrue();
+
+		// Search for object identities has to be done in the following order:
+		// last
+		// element have to be one which
+		// is already in cache and the element before it must not be stored in
+		// cache
+		List<ObjectIdentity> allOids = Arrays.asList(grandParentOid, parent1Oid, parent2Oid, childOid);
+		try {
+			foundAcls = strategy.readAclsById(allOids, sids);
+
+		} catch (NotFoundException notExpected) {
+			fail("It shouldn't have thrown NotFoundException");
+		}
+
+		Acl foundParent2Acl = foundAcls.get(parent2Oid);
+		assertThat(foundParent2Acl).isNotNull();
+		assertThat(foundParent2Acl.isGranted(checkPermission, sids, false)).isTrue();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void nullOwnerIsNotSupported() {
+		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,104,null,null,1);";
+
+		getJdbcTemplate().execute(query);
+
+		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(104));
+
+		strategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID));
+	}
+
+	@Test
+	public void testCreatePrincipalSid() {
+		Sid result = strategy.createSid(true, "sid");
+
+		assertThat(result.getClass()).isEqualTo(PrincipalSid.class);
+		assertThat(((PrincipalSid) result).getPrincipal()).isEqualTo("sid");
+	}
+
+	@Test
+	public void testCreateGrantedAuthority() {
+		Sid result = strategy.createSid(false, "sid");
+
+		assertThat(result.getClass()).isEqualTo(GrantedAuthoritySid.class);
+		assertThat(((GrantedAuthoritySid) result).getGrantedAuthority()).isEqualTo("sid");
+	}
+
+}

+ 160 - 0
acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTest.java

@@ -0,0 +1,160 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * 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.springframework.security.acls.jdbc;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.core.convert.ConversionService;
+
+/**
+ * Tests for {@link AclClassIdUtils}.
+ * @author paulwheeler
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AclClassIdUtilsTest {
+
+	private static final Long DEFAULT_IDENTIFIER = 999L;
+	private static final String DEFAULT_IDENTIFIER_AS_STRING = DEFAULT_IDENTIFIER.toString();
+
+	@Mock
+	private ResultSet resultSet;
+	@Mock
+	private ConversionService conversionService;
+	@InjectMocks
+	private AclClassIdUtils aclClassIdUtils;
+
+	@Before
+	public void setUp() throws Exception {
+		given(conversionService.canConvert(String.class, Long.class)).willReturn(true);
+		given(conversionService.convert(DEFAULT_IDENTIFIER, Long.class)).willReturn(new Long(DEFAULT_IDENTIFIER));
+		given(conversionService.convert(DEFAULT_IDENTIFIER_AS_STRING, Long.class)).willReturn(new Long(DEFAULT_IDENTIFIER));
+	}
+
+	@Test
+	public void shouldReturnLongIfIdentifierIsNotStringAndNoConversionService() throws SQLException {
+		// given
+		AclClassIdUtils aclClassIdUtilsWithoutConversionSvc = new AclClassIdUtils();
+
+		// when
+		Serializable newIdentifier = aclClassIdUtilsWithoutConversionSvc.identifierFrom(DEFAULT_IDENTIFIER, resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER);
+	}
+
+	@Test
+	public void shouldReturnLongIfIdentifierIsNotString() throws SQLException {
+		// given
+		Long prevIdentifier = 999L;
+
+		// when
+		Serializable newIdentifier = aclClassIdUtils.identifierFrom(prevIdentifier, resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(prevIdentifier);
+	}
+
+	@Test
+	public void shouldReturnLongIfClassIdTypeIsNull() throws SQLException {
+		// given
+		given(resultSet.getString("class_id_type")).willReturn(null);
+
+		// when
+		Serializable newIdentifier = aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER);
+	}
+
+	@Test
+	public void shouldReturnLongIfNoClassIdTypeColumn() throws SQLException {
+		// given
+		given(resultSet.getString("class_id_type")).willThrow(SQLException.class);
+
+		// when
+		Serializable newIdentifier = aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER);
+	}
+
+	@Test
+	public void shouldReturnLongIfTypeClassNotFound() throws SQLException {
+		// given
+		given(resultSet.getString("class_id_type")).willReturn("com.example.UnknownType");
+
+		// when
+		Serializable newIdentifier = aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER);
+	}
+
+	@Test
+	public void shouldReturnLongIfTypeClassCannotBeConverted() throws SQLException {
+		// given
+		given(resultSet.getString("class_id_type")).willReturn("java.lang.Long");
+		given(conversionService.canConvert(String.class, Long.class)).willReturn(false);
+
+		// when
+		Serializable newIdentifier = aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER);
+	}
+
+	@Test
+	public void shouldReturnLongWhenLongClassIdType() throws SQLException {
+		// given
+		given(resultSet.getString("class_id_type")).willReturn("java.lang.Long");
+
+		// when
+		Serializable newIdentifier = aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER);
+	}
+
+	@Test
+	public void shouldReturnUUIDWhenUUIDClassIdType() throws SQLException {
+		// given
+		UUID identifier = UUID.randomUUID();
+		String identifierAsString = identifier.toString();
+		given(resultSet.getString("class_id_type")).willReturn("java.util.UUID");
+		given(conversionService.canConvert(String.class, UUID.class)).willReturn(true);
+		given(conversionService.convert(identifierAsString, UUID.class)).willReturn(UUID.fromString(identifierAsString));
+
+		// when
+		Serializable newIdentifier = aclClassIdUtils.identifierFrom(identifier.toString(), resultSet);
+
+		// then
+		assertThat(newIdentifier).isEqualTo(identifier);
+	}
+
+}

+ 16 - 307
acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2017 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,330 +15,39 @@
  */
 package org.springframework.security.acls.jdbc;
 
-import static org.assertj.core.api.Assertions.*;
+import javax.sql.DataSource;
 
-import net.sf.ehcache.Cache;
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
-import org.junit.*;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.datasource.SingleConnectionDataSource;
-import org.springframework.security.acls.domain.*;
-import org.springframework.security.acls.model.Acl;
-import org.springframework.security.acls.model.AuditableAccessControlEntry;
-import org.springframework.security.acls.model.MutableAcl;
-import org.springframework.security.acls.model.NotFoundException;
-import org.springframework.security.acls.model.ObjectIdentity;
-import org.springframework.security.acls.model.Permission;
-import org.springframework.security.acls.model.Sid;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.util.FileCopyUtils;
-
-import java.util.*;
 
 /**
- * Tests {@link BasicLookupStrategy}
+ * Tests {@link BasicLookupStrategy} with Acl Class type id not specified.
  *
  * @author Andrei Stefan
+ * @author Paul Wheeler
  */
-public class BasicLookupStrategyTests {
-
-	private static final Sid BEN_SID = new PrincipalSid("ben");
-	private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
-
-	// ~ Instance fields
-	// ================================================================================================
-
-	private static JdbcTemplate jdbcTemplate;
-	private BasicLookupStrategy strategy;
-	private static SingleConnectionDataSource dataSource;
-	private static CacheManager cacheManager;
+public class BasicLookupStrategyTests extends AbstractBasicLookupStrategyTests {
+	private static final BasicLookupStrategyTestsDbHelper DATABASE_HELPER = new BasicLookupStrategyTestsDbHelper();
 
-	// ~ Methods
-	// ========================================================================================================
-	@BeforeClass
-	public static void initCacheManaer() {
-		cacheManager = CacheManager.create();
-		cacheManager.addCache(new Cache("basiclookuptestcache", 500, false, false, 30, 30));
-	}
 
 	@BeforeClass
 	public static void createDatabase() throws Exception {
-		dataSource = new SingleConnectionDataSource("jdbc:hsqldb:mem:lookupstrategytest", "sa", "", true);
-		dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
-		jdbcTemplate = new JdbcTemplate(dataSource);
-
-		Resource resource = new ClassPathResource("createAclSchema.sql");
-		String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
-		jdbcTemplate.execute(sql);
+		DATABASE_HELPER.createDatabase();
 	}
 
 	@AfterClass
 	public static void dropDatabase() throws Exception {
-		dataSource.destroy();
-	}
-
-	@AfterClass
-	public static void shutdownCacheManager() {
-		cacheManager.removalAll();
-		cacheManager.shutdown();
-	}
-
-	@Before
-	public void populateDatabase() {
-		String query = "INSERT INTO acl_sid(ID,PRINCIPAL,SID) VALUES (1,1,'ben');"
-				+ "INSERT INTO acl_class(ID,CLASS) VALUES (2,'" + TARGET_CLASS + "');"
-				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (1,2,100,null,1,1);"
-				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (2,2,101,1,1,1);"
-				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (3,2,102,2,1,1);"
-				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (1,1,0,1,1,1,0,0);"
-				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (2,1,1,1,2,0,0,0);"
-				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (3,2,0,1,8,1,0,0);"
-				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (4,3,0,1,8,0,0,0);";
-		jdbcTemplate.execute(query);
-	}
-
-	@Before
-	public void initializeBeans() {
-		EhCacheBasedAclCache cache = new EhCacheBasedAclCache(getCache(),
-				new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()),
-				new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
-		AclAuthorizationStrategy authorizationStrategy = new AclAuthorizationStrategyImpl(
-				new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
-		strategy = new BasicLookupStrategy(dataSource, cache, authorizationStrategy,
-				new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()));
-		strategy.setPermissionFactory(new DefaultPermissionFactory());
-	}
-
-	@After
-	public void emptyDatabase() {
-		String query = "DELETE FROM acl_entry;" + "DELETE FROM acl_object_identity WHERE ID = 7;"
-				+ "DELETE FROM acl_object_identity WHERE ID = 6;" + "DELETE FROM acl_object_identity WHERE ID = 5;"
-				+ "DELETE FROM acl_object_identity WHERE ID = 4;" + "DELETE FROM acl_object_identity WHERE ID = 3;"
-				+ "DELETE FROM acl_object_identity WHERE ID = 2;" + "DELETE FROM acl_object_identity WHERE ID = 1;"
-				+ "DELETE FROM acl_class;" + "DELETE FROM acl_sid;";
-		jdbcTemplate.execute(query);
+		DATABASE_HELPER.getDataSource().destroy();
 	}
 
-	private Ehcache getCache() {
-		Ehcache cache = cacheManager.getCache("basiclookuptestcache");
-		cache.removeAll();
-		return cache;
+	@Override
+	public JdbcTemplate getJdbcTemplate() {
+		return DATABASE_HELPER.getJdbcTemplate();
 	}
 
-	@Test
-	public void testAclsRetrievalWithDefaultBatchSize() throws Exception {
-		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
-		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(101));
-		// Deliberately use an integer for the child, to reproduce bug report in
-		// SEC-819
-		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(102));
-
-		Map<ObjectIdentity, Acl> map = this.strategy
-				.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-		checkEntries(topParentOid, middleParentOid, childOid, map);
-	}
-
-	@Test
-	public void testAclsRetrievalFromCacheOnly() throws Exception {
-		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(100));
-		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(101));
-		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
-
-		// Objects were put in cache
-		strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-
-		// Let's empty the database to force acls retrieval from cache
-		emptyDatabase();
-		Map<ObjectIdentity, Acl> map = this.strategy
-				.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-
-		checkEntries(topParentOid, middleParentOid, childOid, map);
-	}
-
-	@Test
-	public void testAclsRetrievalWithCustomBatchSize() throws Exception {
-		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
-		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(101));
-		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
-
-		// Set a batch size to allow multiple database queries in order to
-		// retrieve all
-		// acls
-		this.strategy.setBatchSize(1);
-		Map<ObjectIdentity, Acl> map = this.strategy
-				.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-		checkEntries(topParentOid, middleParentOid, childOid, map);
+	@Override
+	public DataSource getDataSource() {
+		return DATABASE_HELPER.getDataSource();
 	}
-
-	private void checkEntries(ObjectIdentity topParentOid, ObjectIdentity middleParentOid, ObjectIdentity childOid,
-			Map<ObjectIdentity, Acl> map) throws Exception {
-		assertThat(map).hasSize(3);
-
-		MutableAcl topParent = (MutableAcl) map.get(topParentOid);
-		MutableAcl middleParent = (MutableAcl) map.get(middleParentOid);
-		MutableAcl child = (MutableAcl) map.get(childOid);
-
-		// Check the retrieved versions has IDs
-		assertThat(topParent.getId()).isNotNull();
-		assertThat(middleParent.getId()).isNotNull();
-		assertThat(child.getId()).isNotNull();
-
-		// Check their parents were correctly retrieved
-		assertThat(topParent.getParentAcl()).isNull();
-		assertThat(middleParent.getParentAcl().getObjectIdentity()).isEqualTo(topParentOid);
-		assertThat(child.getParentAcl().getObjectIdentity()).isEqualTo(middleParentOid);
-
-		// Check their ACEs were correctly retrieved
-		assertThat(topParent.getEntries()).hasSize(2);
-		assertThat(middleParent.getEntries()).hasSize(1);
-		assertThat(child.getEntries()).hasSize(1);
-
-		// Check object identities were correctly retrieved
-		assertThat(topParent.getObjectIdentity()).isEqualTo(topParentOid);
-		assertThat(middleParent.getObjectIdentity()).isEqualTo(middleParentOid);
-		assertThat(child.getObjectIdentity()).isEqualTo(childOid);
-
-		// Check each entry
-		assertThat(topParent.isEntriesInheriting()).isTrue();
-		assertThat(Long.valueOf(1)).isEqualTo(topParent.getId());
-		assertThat(new PrincipalSid("ben")).isEqualTo(topParent.getOwner());
-		assertThat(Long.valueOf(1)).isEqualTo(topParent.getEntries().get(0).getId());
-		assertThat(topParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.READ);
-		assertThat(topParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben"));
-		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditFailure()).isFalse();
-		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditSuccess()).isFalse();
-		assertThat((topParent.getEntries().get(0)).isGranting()).isTrue();
-
-		assertThat(Long.valueOf(2)).isEqualTo(topParent.getEntries().get(1).getId());
-		assertThat(topParent.getEntries().get(1).getPermission()).isEqualTo(BasePermission.WRITE);
-		assertThat(topParent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("ben"));
-		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditFailure()).isFalse();
-		assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditSuccess()).isFalse();
-		assertThat(topParent.getEntries().get(1).isGranting()).isFalse();
-
-		assertThat(middleParent.isEntriesInheriting()).isTrue();
-		assertThat(Long.valueOf(2)).isEqualTo(middleParent.getId());
-		assertThat(new PrincipalSid("ben")).isEqualTo(middleParent.getOwner());
-		assertThat(Long.valueOf(3)).isEqualTo(middleParent.getEntries().get(0).getId());
-		assertThat(middleParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE);
-		assertThat(middleParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben"));
-		assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditFailure()).isFalse();
-		assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditSuccess()).isFalse();
-		assertThat(middleParent.getEntries().get(0).isGranting()).isTrue();
-
-		assertThat(child.isEntriesInheriting()).isTrue();
-		assertThat(Long.valueOf(3)).isEqualTo(child.getId());
-		assertThat(new PrincipalSid("ben")).isEqualTo(child.getOwner());
-		assertThat(Long.valueOf(4)).isEqualTo(child.getEntries().get(0).getId());
-		assertThat(child.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE);
-		assertThat(new PrincipalSid("ben")).isEqualTo(child.getEntries().get(0).getSid());
-		assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditFailure()).isFalse();
-		assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditSuccess()).isFalse();
-		assertThat((child.getEntries().get(0)).isGranting()).isFalse();
-	}
-
-	@Test
-	public void testAllParentsAreRetrievedWhenChildIsLoaded() throws Exception {
-		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,103,1,1,1);";
-		jdbcTemplate.execute(query);
-
-		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
-		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101));
-		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102));
-		ObjectIdentity middleParent2Oid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(103));
-
-		// Retrieve the child
-		Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(Arrays.asList(childOid), null);
-
-		// Check that the child and all its parents were retrieved
-		assertThat(map.get(childOid)).isNotNull();
-		assertThat(map.get(childOid).getObjectIdentity()).isEqualTo(childOid);
-		assertThat(map.get(middleParentOid)).isNotNull();
-		assertThat(map.get(middleParentOid).getObjectIdentity()).isEqualTo(middleParentOid);
-		assertThat(map.get(topParentOid)).isNotNull();
-		assertThat(map.get(topParentOid).getObjectIdentity()).isEqualTo(topParentOid);
-
-		// The second parent shouldn't have been retrieved
-		assertThat(map.get(middleParent2Oid)).isNull();
-	}
-
-	/**
-	 * Test created from SEC-590.
-	 */
-	@Test
-	public void testReadAllObjectIdentitiesWhenLastElementIsAlreadyCached() throws Exception {
-		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,104,null,1,1);"
-				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (5,2,105,4,1,1);"
-				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,106,4,1,1);"
-				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (7,2,107,5,1,1);"
-				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (5,4,0,1,1,1,0,0)";
-		jdbcTemplate.execute(query);
-
-		ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(104));
-		ObjectIdentity parent1Oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(105));
-		ObjectIdentity parent2Oid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(106));
-		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(107));
-
-		// First lookup only child, thus populating the cache with grandParent,
-		// parent1
-		// and child
-		List<Permission> checkPermission = Arrays.asList(BasePermission.READ);
-		List<Sid> sids = Arrays.asList(BEN_SID);
-		List<ObjectIdentity> childOids = Arrays.asList(childOid);
-
-		strategy.setBatchSize(6);
-		Map<ObjectIdentity, Acl> foundAcls = strategy.readAclsById(childOids, sids);
-
-		Acl foundChildAcl = foundAcls.get(childOid);
-		assertThat(foundChildAcl).isNotNull();
-		assertThat(foundChildAcl.isGranted(checkPermission, sids, false)).isTrue();
-
-		// Search for object identities has to be done in the following order:
-		// last
-		// element have to be one which
-		// is already in cache and the element before it must not be stored in
-		// cache
-		List<ObjectIdentity> allOids = Arrays.asList(grandParentOid, parent1Oid, parent2Oid, childOid);
-		try {
-			foundAcls = strategy.readAclsById(allOids, sids);
-
-		} catch (NotFoundException notExpected) {
-			fail("It shouldn't have thrown NotFoundException");
-		}
-
-		Acl foundParent2Acl = foundAcls.get(parent2Oid);
-		assertThat(foundParent2Acl).isNotNull();
-		assertThat(foundParent2Acl.isGranted(checkPermission, sids, false)).isTrue();
-	}
-
-	@Test(expected = IllegalArgumentException.class)
-	public void nullOwnerIsNotSupported() {
-		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,104,null,null,1);";
-
-		jdbcTemplate.execute(query);
-
-		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(104));
-
-		strategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID));
-	}
-
-	@Test
-	public void testCreatePrincipalSid() {
-		Sid result = strategy.createSid(true, "sid");
-
-		assertThat(result.getClass()).isEqualTo(PrincipalSid.class);
-		assertThat(((PrincipalSid) result).getPrincipal()).isEqualTo("sid");
-	}
-
-	@Test
-	public void testCreateGrantedAuthority() {
-		Sid result = strategy.createSid(false, "sid");
-
-		assertThat(result.getClass()).isEqualTo(GrantedAuthoritySid.class);
-		assertThat(((GrantedAuthoritySid) result).getGrantedAuthority()).isEqualTo("sid");
-	}
-
 }

+ 72 - 0
acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTestsDbHelper.java

@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * 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.springframework.security.acls.jdbc;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.SingleConnectionDataSource;
+import org.springframework.util.FileCopyUtils;
+
+/**
+ * Helper class to initialize the database for BasicLookupStrategyTests.
+ * @author Andrei Stefan
+ * @author Paul Wheeler
+ */
+public class BasicLookupStrategyTestsDbHelper {
+	private static final String ACL_SCHEMA_SQL_FILE = "createAclSchema.sql";
+	private static final String ACL_SCHEMA_SQL_FILE_WITH_ACL_CLASS_ID = "createAclSchemaWithAclClassIdType.sql";
+
+	private SingleConnectionDataSource dataSource;
+	private JdbcTemplate jdbcTemplate;
+	private boolean withAclClassIdType;
+
+	public BasicLookupStrategyTestsDbHelper() {
+	}
+
+	public BasicLookupStrategyTestsDbHelper(boolean withAclClassIdType) {
+		this.withAclClassIdType = withAclClassIdType;
+	}
+
+	public void createDatabase() throws Exception {
+		// Use a different connection url so the tests can run in parallel
+		String connectionUrl;
+		String sqlClassPathResource;
+		if (!withAclClassIdType) {
+			connectionUrl = "jdbc:hsqldb:mem:lookupstrategytest";
+			sqlClassPathResource = ACL_SCHEMA_SQL_FILE;
+		} else {
+			connectionUrl = "jdbc:hsqldb:mem:lookupstrategytestWithAclClassIdType";
+			sqlClassPathResource = ACL_SCHEMA_SQL_FILE_WITH_ACL_CLASS_ID;
+
+		}
+		dataSource = new SingleConnectionDataSource(connectionUrl, "sa", "", true);
+		dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
+		jdbcTemplate = new JdbcTemplate(dataSource);
+
+		Resource resource = new ClassPathResource(sqlClassPathResource);
+		String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
+		jdbcTemplate.execute(sql);
+	}
+
+	public JdbcTemplate getJdbcTemplate() {
+		return jdbcTemplate;
+	}
+
+	public SingleConnectionDataSource getDataSource() {
+		return dataSource;
+	}
+}

+ 117 - 0
acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyWithAclClassTypeTests.java

@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * 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.springframework.security.acls.jdbc;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.core.convert.ConversionFailedException;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.acls.domain.ConsoleAuditLogger;
+import org.springframework.security.acls.domain.DefaultPermissionFactory;
+import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
+import org.springframework.security.acls.domain.ObjectIdentityImpl;
+import org.springframework.security.acls.model.Acl;
+import org.springframework.security.acls.model.ObjectIdentity;
+
+import junit.framework.Assert;
+
+/**
+ * Tests {@link BasicLookupStrategy} with Acl Class type id set to UUID.
+ *
+ * @author Paul Wheeler
+ */
+public class BasicLookupStrategyWithAclClassTypeTests extends AbstractBasicLookupStrategyTests {
+
+	private static final BasicLookupStrategyTestsDbHelper DATABASE_HELPER = new BasicLookupStrategyTestsDbHelper(true);
+
+	private BasicLookupStrategy uuidEnabledStrategy;
+
+	@Override
+	public JdbcTemplate getJdbcTemplate() {
+		return DATABASE_HELPER.getJdbcTemplate();
+	}
+
+	@Override
+	public DataSource getDataSource() {
+		return DATABASE_HELPER.getDataSource();
+	}
+
+	@BeforeClass
+	public static void createDatabase() throws Exception {
+		DATABASE_HELPER.createDatabase();
+	}
+
+	@AfterClass
+	public static void dropDatabase() throws Exception {
+		DATABASE_HELPER.getDataSource().destroy();
+	}
+
+	@Before
+	public void initializeBeans() {
+		super.initializeBeans();
+		AclClassIdUtils aclClassIdUtils = new AclClassIdUtils();
+		aclClassIdUtils.setConversionService(new DefaultConversionService());
+		uuidEnabledStrategy = new BasicLookupStrategy(getDataSource(), aclCache(), aclAuthStrategy(),
+			new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()));
+		uuidEnabledStrategy.setPermissionFactory(new DefaultPermissionFactory());
+		uuidEnabledStrategy.setAclClassIdSupported(true);
+		uuidEnabledStrategy.setAclClassIdUtils(aclClassIdUtils);
+	}
+
+	@Before
+	public void populateDatabaseForAclClassTypeTests() {
+		String query = "INSERT INTO acl_class(ID,CLASS,CLASS_ID_TYPE) VALUES (3,'"
+			+ TARGET_CLASS_WITH_UUID
+			+ "', 'java.util.UUID');"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,3,'"
+			+ OBJECT_IDENTITY_UUID.toString() + "',null,1,1);"
+			+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (5,3,'"
+			+ OBJECT_IDENTITY_LONG_AS_UUID + "',null,1,1);"
+			+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (5,4,0,1,8,0,0,0);"
+			+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (6,5,0,1,8,0,0,0);";
+		DATABASE_HELPER.getJdbcTemplate().execute(query);
+	}
+
+	@Test
+	public void testReadObjectIdentityUsingUuidType() {
+		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, OBJECT_IDENTITY_UUID);
+		Map<ObjectIdentity, Acl> foundAcls = uuidEnabledStrategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID));
+		Assert.assertEquals(1, foundAcls.size());
+		Assert.assertNotNull(foundAcls.get(oid));
+	}
+
+	@Test
+	public void testReadObjectIdentityUsingLongTypeWithConversionServiceEnabled() {
+		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
+		Map<ObjectIdentity, Acl> foundAcls = uuidEnabledStrategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID));
+		Assert.assertEquals(1, foundAcls.size());
+		Assert.assertNotNull(foundAcls.get(oid));
+	}
+
+	@Test(expected = ConversionFailedException.class)
+	public void testReadObjectIdentityUsingNonUuidInDatabase() {
+		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, OBJECT_IDENTITY_LONG_AS_UUID);
+		uuidEnabledStrategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID));
+	}
+}

+ 57 - 30
acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -98,10 +98,30 @@ public class JdbcMutableAclServiceTests extends
 	// ~ Methods
 	// ========================================================================================================
 
+	protected String getSqlClassPathResource() {
+		return "createAclSchema.sql";
+	}
+
+	protected ObjectIdentity getTopParentOid() {
+		return topParentOid;
+	}
+
+	protected ObjectIdentity getMiddleParentOid() {
+		return middleParentOid;
+	}
+
+	protected ObjectIdentity getChildOid() {
+		return childOid;
+	}
+
+	protected String getTargetClass() {
+		return TARGET_CLASS;
+	}
+
 	@BeforeTransaction
 	public void createTables() throws Exception {
 		try {
-			new DatabaseSeeder(dataSource, new ClassPathResource("createAclSchema.sql"));
+			new DatabaseSeeder(dataSource, new ClassPathResource(getSqlClassPathResource()));
 			// new DatabaseSeeder(dataSource, new
 			// ClassPathResource("createAclSchemaPostgres.sql"));
 		}
@@ -126,9 +146,9 @@ public class JdbcMutableAclServiceTests extends
 	public void testLifecycle() {
 		SecurityContextHolder.getContext().setAuthentication(auth);
 
-		MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
-		MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
-		MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+		MutableAcl topParent = jdbcMutableAclService.createAcl(getTopParentOid());
+		MutableAcl middleParent = jdbcMutableAclService.createAcl(getMiddleParentOid());
+		MutableAcl child = jdbcMutableAclService.createAcl(getChildOid());
 
 		// Specify the inheritance hierarchy
 		middleParent.setParent(topParent);
@@ -147,13 +167,13 @@ public class JdbcMutableAclServiceTests extends
 
 		// Let's check if we can read them back correctly
 		Map<ObjectIdentity, Acl> map = jdbcMutableAclService.readAclsById(Arrays.asList(
-				topParentOid, middleParentOid, childOid));
+			getTopParentOid(), getMiddleParentOid(), getChildOid()));
 		assertThat(map).hasSize(3);
 
 		// Replace our current objects with their retrieved versions
-		topParent = (MutableAcl) map.get(topParentOid);
-		middleParent = (MutableAcl) map.get(middleParentOid);
-		child = (MutableAcl) map.get(childOid);
+		topParent = (MutableAcl) map.get(getTopParentOid());
+		middleParent = (MutableAcl) map.get(getMiddleParentOid());
+		child = (MutableAcl) map.get(getChildOid());
 
 		// Check the retrieved versions has IDs
 		assertThat(topParent.getId()).isNotNull();
@@ -162,8 +182,8 @@ public class JdbcMutableAclServiceTests extends
 
 		// Check their parents were correctly persisted
 		assertThat(topParent.getParentAcl()).isNull();
-		assertThat(middleParent.getParentAcl().getObjectIdentity()).isEqualTo(topParentOid);
-		assertThat(child.getParentAcl().getObjectIdentity()).isEqualTo(middleParentOid);
+		assertThat(middleParent.getParentAcl().getObjectIdentity()).isEqualTo(getTopParentOid());
+		assertThat(child.getParentAcl().getObjectIdentity()).isEqualTo(getMiddleParentOid());
 
 		// Check their ACEs were correctly persisted
 		assertThat(topParent.getEntries()).hasSize(2);
@@ -197,7 +217,7 @@ public class JdbcMutableAclServiceTests extends
 		// Next change the child so it doesn't inherit permissions from above
 		child.setEntriesInheriting(false);
 		jdbcMutableAclService.updateAcl(child);
-		child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
+		child = (MutableAcl) jdbcMutableAclService.readAclById(getChildOid());
 		assertThat(child.isEntriesInheriting()).isFalse();
 
 		// Check the child permissions no longer inherit
@@ -228,7 +248,7 @@ public class JdbcMutableAclServiceTests extends
 
 		// Save the changed child
 		jdbcMutableAclService.updateAcl(child);
-		child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
+		child = (MutableAcl) jdbcMutableAclService.readAclById(getChildOid());
 		assertThat(child.getEntries()).hasSize(3);
 
 		// Output permissions
@@ -268,38 +288,38 @@ public class JdbcMutableAclServiceTests extends
 	public void deleteAclAlsoDeletesChildren() throws Exception {
 		SecurityContextHolder.getContext().setAuthentication(auth);
 
-		jdbcMutableAclService.createAcl(topParentOid);
-		MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
-		MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+		jdbcMutableAclService.createAcl(getTopParentOid());
+		MutableAcl middleParent = jdbcMutableAclService.createAcl(getMiddleParentOid());
+		MutableAcl child = jdbcMutableAclService.createAcl(getChildOid());
 		child.setParent(middleParent);
 		jdbcMutableAclService.updateAcl(middleParent);
 		jdbcMutableAclService.updateAcl(child);
 		// Check the childOid really is a child of middleParentOid
-		Acl childAcl = jdbcMutableAclService.readAclById(childOid);
+		Acl childAcl = jdbcMutableAclService.readAclById(getChildOid());
 
-		assertThat(childAcl.getParentAcl().getObjectIdentity()).isEqualTo(middleParentOid);
+		assertThat(childAcl.getParentAcl().getObjectIdentity()).isEqualTo(getMiddleParentOid());
 
 		// Delete the mid-parent and test if the child was deleted, as well
-		jdbcMutableAclService.deleteAcl(middleParentOid, true);
+		jdbcMutableAclService.deleteAcl(getMiddleParentOid(), true);
 
 		try {
-			jdbcMutableAclService.readAclById(middleParentOid);
+			jdbcMutableAclService.readAclById(getMiddleParentOid());
 			fail("It should have thrown NotFoundException");
 		}
 		catch (NotFoundException expected) {
 
 		}
 		try {
-			jdbcMutableAclService.readAclById(childOid);
+			jdbcMutableAclService.readAclById(getChildOid());
 			fail("It should have thrown NotFoundException");
 		}
 		catch (NotFoundException expected) {
 
 		}
 
-		Acl acl = jdbcMutableAclService.readAclById(topParentOid);
+		Acl acl = jdbcMutableAclService.readAclById(getTopParentOid());
 		assertThat(acl).isNotNull();
-		assertThat(topParentOid).isEqualTo(((MutableAcl) acl).getObjectIdentity());
+		assertThat(getTopParentOid()).isEqualTo(((MutableAcl) acl).getObjectIdentity());
 	}
 
 	@Test
@@ -367,8 +387,8 @@ public class JdbcMutableAclServiceTests extends
 	@Transactional
 	public void deleteAclWithChildrenThrowsException() throws Exception {
 		SecurityContextHolder.getContext().setAuthentication(auth);
-		MutableAcl parent = jdbcMutableAclService.createAcl(topParentOid);
-		MutableAcl child = jdbcMutableAclService.createAcl(middleParentOid);
+		MutableAcl parent = jdbcMutableAclService.createAcl(getTopParentOid());
+		MutableAcl child = jdbcMutableAclService.createAcl(getMiddleParentOid());
 
 		// Specify the inheritance hierarchy
 		child.setParent(parent);
@@ -378,7 +398,7 @@ public class JdbcMutableAclServiceTests extends
 			jdbcMutableAclService.setForeignKeysInDatabase(false); // switch on FK
 																	// checking in the
 																	// class, not database
-			jdbcMutableAclService.deleteAcl(topParentOid, false);
+			jdbcMutableAclService.deleteAcl(getTopParentOid(), false);
 			fail("It should have thrown ChildrenExistException");
 		}
 		catch (ChildrenExistException expected) {
@@ -393,21 +413,21 @@ public class JdbcMutableAclServiceTests extends
 	@Transactional
 	public void deleteAclRemovesRowsFromDatabase() throws Exception {
 		SecurityContextHolder.getContext().setAuthentication(auth);
-		MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+		MutableAcl child = jdbcMutableAclService.createAcl(getChildOid());
 		child.insertAce(0, BasePermission.DELETE, new PrincipalSid(auth), false);
 		jdbcMutableAclService.updateAcl(child);
 
 		// Remove the child and check all related database rows were removed accordingly
-		jdbcMutableAclService.deleteAcl(childOid, false);
+		jdbcMutableAclService.deleteAcl(getChildOid(), false);
 		assertThat(
 				jdbcTemplate.queryForList(SELECT_ALL_CLASSES,
-						new Object[] { TARGET_CLASS })).hasSize(1);
+						new Object[] { getTargetClass() })).hasSize(1);
 		assertThat(jdbcTemplate.queryForList("select * from acl_object_identity")
 				).isEmpty();
 		assertThat(jdbcTemplate.queryForList("select * from acl_entry")).isEmpty();
 
 		// Check the cache
-		assertThat(aclCache.getFromCache(childOid)).isNull();
+		assertThat(aclCache.getFromCache(getChildOid())).isNull();
 		assertThat(aclCache.getFromCache(Long.valueOf(102))).isNull();
 	}
 
@@ -573,4 +593,11 @@ public class JdbcMutableAclServiceTests extends
 		}
 	}
 
+	protected Authentication getAuth() {
+		return auth;
+	}
+
+	protected JdbcMutableAclService getJdbcMutableAclService() {
+		return jdbcMutableAclService;
+	}
 }

+ 83 - 0
acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTestsWithAclClassId.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * 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.springframework.security.acls.jdbc;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.UUID;
+
+import org.junit.Test;
+import org.springframework.security.acls.TargetObjectWithUUID;
+import org.springframework.security.acls.domain.ObjectIdentityImpl;
+import org.springframework.security.acls.model.ObjectIdentity;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests the ACL system using ACL class id type of UUID and using an in-memory database.
+ * @author Paul Wheeler
+ */
+@ContextConfiguration(locations = {"/jdbcMutableAclServiceTestsWithAclClass-context.xml"})
+public class JdbcMutableAclServiceTestsWithAclClassId extends JdbcMutableAclServiceTests {
+
+	private static final String TARGET_CLASS_WITH_UUID = TargetObjectWithUUID.class.getName();
+
+	private final ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID,
+		UUID.randomUUID());
+	private final ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID,
+		UUID.randomUUID());
+	private final ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID,
+		UUID.randomUUID());
+
+	@Override
+	protected String getSqlClassPathResource() {
+		return "createAclSchemaWithAclClassIdType.sql";
+	}
+
+	@Override
+	protected ObjectIdentity getTopParentOid() {
+		return topParentOid;
+	}
+
+	@Override
+	protected ObjectIdentity getMiddleParentOid() {
+		return middleParentOid;
+	}
+
+	@Override
+	protected ObjectIdentity getChildOid() {
+		return childOid;
+	}
+
+	@Override
+	protected String getTargetClass() {
+		return TARGET_CLASS_WITH_UUID;
+	}
+
+	@Test
+	@Transactional
+	public void identityWithUuidIdIsSupportedByCreateAcl() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(getAuth());
+
+		UUID id = UUID.randomUUID();
+		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, id);
+		getJdbcMutableAclService().createAcl(oid);
+
+		assertThat(getJdbcMutableAclService().readAclById(new ObjectIdentityImpl(
+			TARGET_CLASS_WITH_UUID, id))).isNotNull();
+	}
+}

+ 4 - 1
acl/src/test/resources/jdbcMutableAclServiceTests-context.xml

@@ -17,7 +17,10 @@
 		<constructor-arg>
 		   <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
 			  <property name="cacheManager">
-				<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
+					<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
+						<!-- This context is used in two tests so accept existing cache manager as the context will be reused -->
+						<property name="acceptExisting" value="true"/>
+					</bean>
 			  </property>
 			  <property name="cacheName" value="aclCache"/>
 		   </bean>

+ 33 - 0
acl/src/test/resources/jdbcMutableAclServiceTestsWithAclClass-context.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+  <import resource="jdbcMutableAclServiceTests-context.xml"/>
+
+  <bean id="aclClassIdUtils" class="org.springframework.security.acls.jdbc.AclClassIdUtils">
+    <property name="conversionService">
+      <bean class="org.springframework.core.convert.support.DefaultConversionService"/>
+    </property>
+  </bean>
+  <!-- Overridden bean definitions -->
+
+  <bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
+    <constructor-arg ref="dataSource"/>
+    <constructor-arg ref="lookupStrategy"/>
+    <constructor-arg ref="aclCache"/>
+    <property name="aclClassIdSupported" value="true"/>
+    <property name="aclClassIdUtils" ref="aclClassIdUtils"/>
+  </bean>
+
+  <bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
+    <constructor-arg ref="dataSource"/>
+    <constructor-arg ref="aclCache"/>
+    <constructor-arg ref="aclAuthorizationStrategy"/>
+    <constructor-arg>
+      <bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
+    </constructor-arg>
+    <property name="aclClassIdSupported" value="true"/>
+    <property name="aclClassIdUtils" ref="aclClassIdUtils"/>
+  </bean>
+</beans>

+ 5 - 5
docs/manual/src/docs/asciidoc/index.adoc

@@ -7313,7 +7313,7 @@ create table acl_class(
 create table acl_object_identity(
 	id bigint generated by default as identity(start with 100) not null primary key,
 	object_id_class bigint not null,
-	object_id_identity bigint not null,
+	object_id_identity varchar_ignorecase(36) not null,
 	parent_object bigint,
 	owner_sid bigint,
 	entries_inheriting boolean not null,
@@ -7357,7 +7357,7 @@ create table acl_class(
 create table acl_object_identity(
 	id bigserial primary key,
 	object_id_class bigint not null,
-	object_id_identity bigint not null,
+	object_id_identity varchar(36) not null,
 	parent_object bigint,
 	owner_sid bigint,
 	entries_inheriting boolean not null,
@@ -7406,7 +7406,7 @@ CREATE TABLE acl_class (
 CREATE TABLE acl_object_identity (
 	id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
 	object_id_class BIGINT UNSIGNED NOT NULL,
-	object_id_identity BIGINT NOT NULL,
+	object_id_identity VARCHAR(36) NOT NULL,
 	parent_object BIGINT UNSIGNED,
 	owner_sid BIGINT UNSIGNED,
 	entries_inheriting BOOLEAN NOT NULL,
@@ -7450,7 +7450,7 @@ CREATE TABLE acl_class (
 CREATE TABLE acl_object_identity (
 	id BIGINT NOT NULL IDENTITY PRIMARY KEY,
 	object_id_class BIGINT NOT NULL,
-	object_id_identity BIGINT NOT NULL,
+	object_id_identity VARCHAR(36) NOT NULL,
 	parent_object BIGINT,
 	owner_sid BIGINT,
 	entries_inheriting BIT NOT NULL,
@@ -7508,7 +7508,7 @@ END;
 CREATE TABLE acl_object_identity (
 	id NUMBER(38) NOT NULL PRIMARY KEY,
 	object_id_class NUMBER(38) NOT NULL,
-	object_id_identity NUMBER(38) NOT NULL,
+	object_id_identity NVARCHAR2(36) NOT NULL,
 	parent_object NUMBER(38),
 	owner_sid NUMBER(38),
 	entries_inheriting NUMBER(1) NOT NULL CHECK (entries_inheriting in (0, 1)),

+ 2 - 1
samples/xml/contacts/src/main/java/sample/contact/DataSourcePopulator.java

@@ -95,11 +95,12 @@ public class DataSourcePopulator implements InitializingBean {
 		template.execute("CREATE TABLE ACL_CLASS("
 				+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
 				+ "CLASS VARCHAR_IGNORECASE(100) NOT NULL,"
+				+ "CLASS_ID_TYPE VARCHAR_IGNORECASE(100),"
 				+ "CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
 		template.execute("CREATE TABLE ACL_OBJECT_IDENTITY("
 				+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
 				+ "OBJECT_ID_CLASS BIGINT NOT NULL,"
-				+ "OBJECT_ID_IDENTITY BIGINT NOT NULL,"
+				+ "OBJECT_ID_IDENTITY VARCHAR_IGNORECASE(36) NOT NULL,"
 				+ "PARENT_OBJECT BIGINT,"
 				+ "OWNER_SID BIGINT,"
 				+ "ENTRIES_INHERITING BOOLEAN NOT NULL,"

+ 2 - 2
samples/xml/dms/src/main/java/sample/dms/DataSourcePopulator.java

@@ -48,8 +48,8 @@ public class DataSourcePopulator implements InitializingBean {
 	public void afterPropertiesSet() throws Exception {
 		// ACL tables
 		template.execute("CREATE TABLE ACL_SID(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,PRINCIPAL BOOLEAN NOT NULL,SID VARCHAR_IGNORECASE(100) NOT NULL,CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));");
-		template.execute("CREATE TABLE ACL_CLASS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,CLASS VARCHAR_IGNORECASE(100) NOT NULL,CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
-		template.execute("CREATE TABLE ACL_OBJECT_IDENTITY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,OBJECT_ID_CLASS BIGINT NOT NULL,OBJECT_ID_IDENTITY BIGINT NOT NULL,PARENT_OBJECT BIGINT,OWNER_SID BIGINT,ENTRIES_INHERITING BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));");
+		template.execute("CREATE TABLE ACL_CLASS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,CLASS VARCHAR_IGNORECASE(100) NOT NULL,CLASS_ID_TYPE VARCHAR_IGNORECASE(100),CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
+		template.execute("CREATE TABLE ACL_OBJECT_IDENTITY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,OBJECT_ID_CLASS BIGINT NOT NULL,OBJECT_ID_IDENTITY VARCHAR_IGNORECASE(36) NOT NULL,PARENT_OBJECT BIGINT,OWNER_SID BIGINT,ENTRIES_INHERITING BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));");
 		template.execute("CREATE TABLE ACL_ENTRY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY BIGINT NOT NULL,ACE_ORDER INT NOT NULL,SID BIGINT NOT NULL,MASK INTEGER NOT NULL,GRANTING BOOLEAN NOT NULL,AUDIT_SUCCESS BOOLEAN NOT NULL,AUDIT_FAILURE BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));");
 
 		// Normal authentication tables