Skip to content

Commit cd8d207

Browse files
pwheelthomasdarimont
authored andcommitted
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 spring-projectsgh-1224
1 parent ce6f3f4 commit cd8d207

22 files changed

+1200
-368
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.acls.jdbc;
17+
18+
import java.io.Serializable;
19+
import java.sql.ResultSet;
20+
import java.sql.SQLException;
21+
22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
24+
import org.springframework.core.convert.ConversionService;
25+
import org.springframework.security.acls.model.ObjectIdentity;
26+
27+
/**
28+
* Utility class for helping convert database representations of {@link ObjectIdentity#getIdentifier()} into
29+
* the correct Java type as specified by <code>acl_class.class_id_type</code>.
30+
* @author paulwheeler
31+
*/
32+
class AclClassIdUtils {
33+
private static final String DEFAULT_CLASS_ID_TYPE_COLUMN_NAME = "class_id_type";
34+
private static final Log log = LogFactory.getLog(AclClassIdUtils.class);
35+
36+
private ConversionService conversionService;
37+
38+
public AclClassIdUtils() {
39+
}
40+
41+
/**
42+
* Converts the raw type from the database into the right Java type. For most applications the 'raw type' will be Long, for some applications
43+
* it could be String.
44+
* @param identifier The identifier from the database
45+
* @param resultSet Result set of the query
46+
* @return The identifier in the appropriate target Java type. Typically Long or UUID.
47+
* @throws SQLException
48+
*/
49+
Serializable identifierFrom(Serializable identifier, ResultSet resultSet) throws SQLException {
50+
if (isString(identifier) && hasValidClassIdType(resultSet)
51+
&& canConvertFromStringTo(classIdTypeFrom(resultSet))) {
52+
53+
identifier = convertFromStringTo((String) identifier, classIdTypeFrom(resultSet));
54+
} else {
55+
// Assume it should be a Long type
56+
identifier = convertToLong(identifier);
57+
}
58+
59+
return identifier;
60+
}
61+
62+
private boolean hasValidClassIdType(ResultSet resultSet) throws SQLException {
63+
boolean hasClassIdType = false;
64+
try {
65+
hasClassIdType = classIdTypeFrom(resultSet) != null;
66+
} catch (SQLException e) {
67+
log.debug("Unable to obtain the class id type", e);
68+
}
69+
return hasClassIdType;
70+
}
71+
72+
private <T extends Serializable> Class<T> classIdTypeFrom(ResultSet resultSet) throws SQLException {
73+
return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME));
74+
}
75+
76+
private <T extends Serializable> Class<T> classIdTypeFrom(String className) {
77+
Class targetType = null;
78+
if (className != null) {
79+
try {
80+
targetType = Class.forName(className);
81+
} catch (ClassNotFoundException e) {
82+
log.debug("Unable to find class id type on classpath", e);
83+
}
84+
}
85+
return targetType;
86+
}
87+
88+
private <T> boolean canConvertFromStringTo(Class<T> targetType) {
89+
return hasConversionService() && conversionService.canConvert(String.class, targetType);
90+
}
91+
92+
private <T extends Serializable> T convertFromStringTo(String identifier, Class<T> targetType) {
93+
return conversionService.convert(identifier, targetType);
94+
}
95+
96+
private boolean hasConversionService() {
97+
return conversionService != null;
98+
}
99+
100+
/**
101+
* Converts to a {@link Long}, attempting to use the {@link ConversionService} if available.
102+
* @param identifier The identifier
103+
* @return Long version of the identifier
104+
* @throws NumberFormatException if the string cannot be parsed to a long.
105+
* @throws org.springframework.core.convert.ConversionException if a conversion exception occurred
106+
* @throws IllegalArgumentException if targetType is null
107+
*/
108+
private Long convertToLong(Serializable identifier) {
109+
Long idAsLong;
110+
if (hasConversionService()) {
111+
idAsLong = conversionService.convert(identifier, Long.class);
112+
} else {
113+
idAsLong = Long.valueOf(identifier.toString());
114+
}
115+
return idAsLong;
116+
}
117+
118+
private boolean isString(Serializable object) {
119+
return object.getClass().isAssignableFrom(String.class);
120+
}
121+
122+
public void setConversionService(ConversionService conversionService) {
123+
this.conversionService = conversionService;
124+
}
125+
}

acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2+
* Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,9 @@
3030

3131
import javax.sql.DataSource;
3232

33+
import org.springframework.core.convert.ConversionException;
34+
import org.springframework.core.convert.ConversionService;
35+
import org.springframework.core.convert.support.DefaultConversionService;
3336
import org.springframework.jdbc.core.JdbcTemplate;
3437
import org.springframework.jdbc.core.PreparedStatementSetter;
3538
import org.springframework.jdbc.core.ResultSetExtractor;
@@ -78,7 +81,7 @@
7881
*/
7982
public class BasicLookupStrategy implements LookupStrategy {
8083

81-
public final static String DEFAULT_SELECT_CLAUSE = "select acl_object_identity.object_id_identity, "
84+
private final static String DEFAULT_SELECT_CLAUSE_COLUMNS = "select acl_object_identity.object_id_identity, "
8285
+ "acl_entry.ace_order, "
8386
+ "acl_object_identity.id as acl_id, "
8487
+ "acl_object_identity.parent_object, "
@@ -92,13 +95,19 @@ public class BasicLookupStrategy implements LookupStrategy {
9295
+ "acl_sid.sid as ace_sid, "
9396
+ "acli_sid.principal as acl_principal, "
9497
+ "acli_sid.sid as acl_sid, "
95-
+ "acl_class.class "
96-
+ "from acl_object_identity "
98+
+ "acl_class.class ";
99+
private final static String DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN = ", acl_class.class_id_type ";
100+
private final static String DEFAULT_SELECT_CLAUSE_FROM = "from acl_object_identity "
97101
+ "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid "
98102
+ "left join acl_class on acl_class.id = acl_object_identity.object_id_class "
99103
+ "left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity "
100104
+ "left join acl_sid on acl_entry.sid = acl_sid.id " + "where ( ";
101105

106+
public final static String DEFAULT_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS + DEFAULT_SELECT_CLAUSE_FROM;
107+
108+
public final static String DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS +
109+
DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN + DEFAULT_SELECT_CLAUSE_FROM;
110+
102111
private final static String DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE = "(acl_object_identity.id = ?)";
103112

104113
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 {
126135
private String lookupObjectIdentitiesWhereClause = DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE;
127136
private String orderByClause = DEFAULT_ORDER_BY_CLAUSE;
128137

138+
private AclClassIdUtils aclClassIdUtils;
139+
129140
// ~ Constructors
130141
// ===================================================================================================
131142

@@ -161,9 +172,9 @@ public BasicLookupStrategy(DataSource dataSource, AclCache aclCache,
161172
this.aclCache = aclCache;
162173
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
163174
this.grantingStrategy = grantingStrategy;
175+
this.aclClassIdUtils = new AclClassIdUtils();
164176
fieldAces.setAccessible(true);
165177
fieldAcl.setAccessible(true);
166-
167178
}
168179

169180
// ~ Methods
@@ -383,10 +394,9 @@ public void setValues(PreparedStatement ps) throws SQLException {
383394
// No need to check for nulls, as guaranteed non-null by
384395
// ObjectIdentity.getIdentifier() interface contract
385396
String identifier = oid.getIdentifier().toString();
386-
long id = (Long.valueOf(identifier)).longValue();
387397

388398
// Inject values
389-
ps.setLong((2 * i) + 1, id);
399+
ps.setString((2 * i) + 1, identifier);
390400
ps.setString((2 * i) + 2, type);
391401
i++;
392402
}
@@ -537,6 +547,18 @@ public final void setOrderByClause(String orderByClause) {
537547
this.orderByClause = orderByClause;
538548
}
539549

550+
public final void setAclClassIdSupported(boolean aclClassIdSupported) {
551+
if (aclClassIdSupported) {
552+
Assert.isTrue(this.selectClause.equals(DEFAULT_SELECT_CLAUSE), "Cannot set aclClassIdSupported and override the select clause; "
553+
+ "just override the select clause");
554+
this.selectClause = DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE;
555+
}
556+
}
557+
558+
public final void setAclClassIdUtils(AclClassIdUtils aclClassIdUtils) {
559+
this.aclClassIdUtils = aclClassIdUtils;
560+
}
561+
540562
// ~ Inner Classes
541563
// ==================================================================================================
542564

@@ -602,6 +624,7 @@ public Set<Long> extractData(ResultSet rs) throws SQLException {
602624
* @param rs the ResultSet focused on a current row
603625
*
604626
* @throws SQLException if something goes wrong converting values
627+
* @throws ConversionException if can't convert to the desired Java type
605628
*/
606629
private void convertCurrentResultIntoObject(Map<Serializable, Acl> acls,
607630
ResultSet rs) throws SQLException {
@@ -612,9 +635,12 @@ private void convertCurrentResultIntoObject(Map<Serializable, Acl> acls,
612635

613636
if (acl == null) {
614637
// Make an AclImpl and pop it into the Map
638+
639+
// If the Java type is a String, check to see if we can convert it to the target id type, e.g. UUID.
640+
Serializable identifier = (Serializable) rs.getObject("object_id_identity");
641+
identifier = aclClassIdUtils.identifierFrom(identifier, rs);
615642
ObjectIdentity objectIdentity = new ObjectIdentityImpl(
616-
rs.getString("class"), Long.valueOf(rs
617-
.getLong("object_id_identity")));
643+
rs.getString("class"), identifier);
618644

619645
Acl parentAcl = null;
620646
long parentAclId = rs.getLong("parent_object");

acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2+
* Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.security.acls.jdbc;
1717

18+
import java.io.Serializable;
1819
import java.sql.ResultSet;
1920
import java.sql.SQLException;
2021
import java.util.Arrays;
@@ -49,8 +50,15 @@ public class JdbcAclService implements AclService {
4950
// =====================================================================================
5051

5152
protected static final Log log = LogFactory.getLog(JdbcAclService.class);
52-
private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, class.class as class "
53-
+ "from acl_object_identity obj, acl_object_identity parent, acl_class class "
53+
private static final String DEFAULT_SELECT_ACL_CLASS_COLUMNS = "class.class as class";
54+
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";
55+
private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, " + DEFAULT_SELECT_ACL_CLASS_COLUMNS
56+
+ " from acl_object_identity obj, acl_object_identity parent, acl_class class "
57+
+ "where obj.parent_object = parent.id and obj.object_id_class = class.id "
58+
+ "and parent.object_id_identity = ? and parent.object_id_class = ("
59+
+ "select id FROM acl_class where acl_class.class = ?)";
60+
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
61+
+ " from acl_object_identity obj, acl_object_identity parent, acl_class class "
5462
+ "where obj.parent_object = parent.id and obj.object_id_class = class.id "
5563
+ "and parent.object_id_identity = ? and parent.object_id_class = ("
5664
+ "select id FROM acl_class where acl_class.class = ?)";
@@ -60,7 +68,9 @@ public class JdbcAclService implements AclService {
6068

6169
protected final JdbcTemplate jdbcTemplate;
6270
private final LookupStrategy lookupStrategy;
71+
private boolean aclClassIdSupported;
6372
private String findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL;
73+
private AclClassIdUtils aclClassIdUtils;
6474

6575
// ~ Constructors
6676
// ===================================================================================================
@@ -70,6 +80,7 @@ public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) {
7080
Assert.notNull(lookupStrategy, "LookupStrategy required");
7181
this.jdbcTemplate = new JdbcTemplate(dataSource);
7282
this.lookupStrategy = lookupStrategy;
83+
this.aclClassIdUtils = new AclClassIdUtils();
7384
}
7485

7586
// ~ Methods
@@ -82,7 +93,8 @@ public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
8293
public ObjectIdentity mapRow(ResultSet rs, int rowNum)
8394
throws SQLException {
8495
String javaType = rs.getString("class");
85-
Long identifier = new Long(rs.getLong("obj_id"));
96+
Serializable identifier = (Serializable) rs.getObject("obj_id");
97+
identifier = aclClassIdUtils.identifierFrom(identifier, rs);
8698

8799
return new ObjectIdentityImpl(javaType, identifier);
88100
}
@@ -138,4 +150,24 @@ public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects,
138150
public void setFindChildrenQuery(String findChildrenSql) {
139151
this.findChildrenSql = findChildrenSql;
140152
}
153+
154+
public void setAclClassIdSupported(boolean aclClassIdSupported) {
155+
this.aclClassIdSupported = aclClassIdSupported;
156+
if (aclClassIdSupported) {
157+
// Change the default insert if it hasn't been overridden
158+
if (this.findChildrenSql.equals(DEFAULT_SELECT_ACL_WITH_PARENT_SQL)) {
159+
this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE;
160+
} else {
161+
log.debug("Find children statement has already been overridden, so not overridding the default");
162+
}
163+
}
164+
}
165+
166+
public void setAclClassIdUtils(AclClassIdUtils aclClassIdUtils) {
167+
this.aclClassIdUtils = aclClassIdUtils;
168+
}
169+
170+
protected boolean isAclClassIdSupported() {
171+
return aclClassIdSupported;
172+
}
141173
}

0 commit comments

Comments
 (0)