diff --git a/acl/spring-security-acl.gradle b/acl/spring-security-acl.gradle index f886c4f7d5..c78a763aba 100644 --- a/acl/spring-security-acl.gradle +++ b/acl/spring-security-acl.gradle @@ -1,5 +1,6 @@ plugins { id 'javadoc-warnings-error' + id 'security-nullability' } apply plugin: 'io.spring.convention.spring-module' diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java index 0e8b013fc4..035ecd7888 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java @@ -18,6 +18,8 @@ package org.springframework.security.acls.domain; import java.io.Serializable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AuditableAccessControlEntry; @@ -36,7 +38,7 @@ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAcce private Permission permission; - private final Serializable id; + private final @Nullable Serializable id; private final Sid sid; @@ -46,7 +48,7 @@ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAcce private final boolean granting; - public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting, + public AccessControlEntryImpl(@Nullable Serializable id, Acl acl, Sid sid, Permission permission, boolean granting, boolean auditSuccess, boolean auditFailure) { Assert.notNull(acl, "Acl required"); Assert.notNull(sid, "Sid required"); @@ -133,7 +135,7 @@ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAcce } @Override - public Serializable getId() { + public @Nullable Serializable getId() { return this.id; } diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java index 07349531b4..685903389b 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java @@ -99,7 +99,8 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { Authentication authentication = context.getAuthentication(); // Check if authorized by virtue of ACL ownership Sid currentUser = createCurrentUser(authentication); - if (currentUser.equals(acl.getOwner()) + Sid owner = acl.getOwner(); + if (owner != null && currentUser.equals(owner) && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) { return; } @@ -108,8 +109,8 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { Collection reachableGrantedAuthorities = this.roleHierarchy .getReachableGrantedAuthorities(authentication.getAuthorities()); Set authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities); - if (acl.getOwner() instanceof GrantedAuthoritySid - && authorities.contains(((GrantedAuthoritySid) acl.getOwner()).getGrantedAuthority())) { + if (owner instanceof GrantedAuthoritySid + && authorities.contains(((GrantedAuthoritySid) owner).getGrantedAuthority())) { return; } diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java index 8550450e8f..0771bc05f5 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java @@ -20,6 +20,8 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AuditableAcl; @@ -41,7 +43,7 @@ import org.springframework.util.ObjectUtils; */ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { - private Acl parentAcl; + private @Nullable Acl parentAcl; private transient AclAuthorizationStrategy aclAuthorizationStrategy; @@ -54,10 +56,10 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { private Serializable id; // OwnershipAcl - private Sid owner; + private @Nullable Sid owner; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID - private List loadedSids = null; + private @Nullable List loadedSids = null; private boolean entriesInheriting = true; @@ -97,8 +99,8 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { * @param owner the owner (required) */ public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy, - PermissionGrantingStrategy grantingStrategy, Acl parentAcl, List loadedSids, boolean entriesInheriting, - Sid owner) { + PermissionGrantingStrategy grantingStrategy, @Nullable Acl parentAcl, @Nullable List loadedSids, + boolean entriesInheriting, Sid owner) { Assert.notNull(objectIdentity, "Object Identity required"); Assert.notNull(id, "Id required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); @@ -117,7 +119,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { * Private no-argument constructor for use by reflection-based persistence tools along * with field-level access. */ - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "NullAway.Init" }) private AclImpl() { } @@ -199,7 +201,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { } @Override - public boolean isSidLoaded(List sids) { + public boolean isSidLoaded(@Nullable List sids) { // If loadedSides is null, this indicates all SIDs were loaded // Also return true if the caller didn't specify a SID to find if ((this.loadedSids == null) || (sids == null) || sids.isEmpty()) { @@ -238,19 +240,19 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { } @Override - public Sid getOwner() { + public @Nullable Sid getOwner() { return this.owner; } @Override - public void setParent(Acl newParent) { + public void setParent(@Nullable Acl newParent) { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); Assert.isTrue(newParent == null || !newParent.equals(this), "Cannot be the parent of yourself"); this.parentAcl = newParent; } @Override - public Acl getParentAcl() { + public @Nullable Acl getParentAcl() { return this.parentAcl; } diff --git a/acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java b/acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java index ab173f68e7..b733941d57 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java @@ -18,6 +18,8 @@ package org.springframework.security.acls.domain; import java.io.Serializable; +import org.jspecify.annotations.Nullable; + import org.springframework.cache.Cache; import org.springframework.security.acls.model.AclCache; import org.springframework.security.acls.model.MutableAcl; @@ -78,13 +80,13 @@ public class SpringCacheBasedAclCache implements AclCache { } @Override - public MutableAcl getFromCache(ObjectIdentity objectIdentity) { + public @Nullable MutableAcl getFromCache(ObjectIdentity objectIdentity) { Assert.notNull(objectIdentity, "ObjectIdentity required"); return getFromCache((Object) objectIdentity); } @Override - public MutableAcl getFromCache(Serializable pk) { + public @Nullable MutableAcl getFromCache(Serializable pk) { Assert.notNull(pk, "Primary key (identifier) required"); return getFromCache((Object) pk); } @@ -101,12 +103,16 @@ public class SpringCacheBasedAclCache implements AclCache { this.cache.put(acl.getId(), acl); } - private MutableAcl getFromCache(Object key) { + private @Nullable MutableAcl getFromCache(Object key) { Cache.ValueWrapper element = this.cache.get(key); if (element == null) { return null; } - return initializeTransientFields((MutableAcl) element.get()); + Object value = element.get(); + if (value == null) { + return null; + } + return initializeTransientFields((MutableAcl) value); } private MutableAcl initializeTransientFields(MutableAcl value) { diff --git a/acl/src/main/java/org/springframework/security/acls/domain/package-info.java b/acl/src/main/java/org/springframework/security/acls/domain/package-info.java index 60248ec587..02c45111a7 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/package-info.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/package-info.java @@ -17,4 +17,7 @@ /** * Basic implementation of access control lists (ACLs) interfaces. */ +@NullMarked package org.springframework.security.acls.domain; + +import org.jspecify.annotations.NullMarked; diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java b/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java index 9a1f5e4da1..17838f5595 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java @@ -23,6 +23,7 @@ import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; @@ -67,10 +68,10 @@ class AclClassIdUtils { * @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))) { - return convertFromStringTo((String) identifier, classIdTypeFrom(resultSet)); + @Nullable Serializable identifierFrom(Serializable identifier, ResultSet resultSet) throws SQLException { + Class classIdType = classIdTypeFrom(resultSet); + if (isString(identifier) && classIdType != null && canConvertFromStringTo(classIdType)) { + return convertFromStringTo((String) identifier, classIdType); } // Assume it should be a Long type return convertToLong(identifier); @@ -86,11 +87,17 @@ class AclClassIdUtils { } } - private Class classIdTypeFrom(ResultSet resultSet) throws SQLException { - return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME)); + private @Nullable Class classIdTypeFrom(ResultSet resultSet) throws SQLException { + try { + return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME)); + } + catch (SQLException ex) { + log.debug("Unable to obtain the class id type", ex); + return null; + } } - private Class classIdTypeFrom(String className) { + private @Nullable Class classIdTypeFrom(String className) { if (className == null) { return null; } @@ -107,7 +114,7 @@ class AclClassIdUtils { return this.conversionService.canConvert(String.class, targetType); } - private T convertFromStringTo(String identifier, Class targetType) { + private @Nullable T convertFromStringTo(String identifier, Class targetType) { return this.conversionService.convert(identifier, targetType); } @@ -121,7 +128,7 @@ class AclClassIdUtils { * exception occurred * @throws IllegalArgumentException if targetType is null */ - private Long convertToLong(Serializable identifier) { + private @Nullable Long convertToLong(Serializable identifier) { if (this.conversionService.canConvert(identifier.getClass(), Long.class)) { return this.conversionService.convert(identifier, Long.class); } @@ -140,10 +147,10 @@ class AclClassIdUtils { private static class StringToLongConverter implements Converter { @Override - public Long convert(String identifierAsString) { + public Long convert(@Nullable String identifierAsString) { if (identifierAsString == null) { throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), - TypeDescriptor.valueOf(Long.class), null, null); + TypeDescriptor.valueOf(Long.class), identifierAsString, new NullPointerException()); } return Long.parseLong(identifierAsString); @@ -154,10 +161,10 @@ class AclClassIdUtils { private static class StringToUUIDConverter implements Converter { @Override - public UUID convert(String identifierAsString) { + public UUID convert(@Nullable String identifierAsString) { if (identifierAsString == null) { throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), - TypeDescriptor.valueOf(UUID.class), null, null); + TypeDescriptor.valueOf(UUID.class), identifierAsString, new NullPointerException()); } return UUID.fromString(identifierAsString); diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java b/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java index 6f5499d19b..b47ea993ab 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java @@ -31,6 +31,8 @@ import java.util.Set; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; import org.springframework.jdbc.core.JdbcTemplate; @@ -224,7 +226,8 @@ public class BasicLookupStrategy implements LookupStrategy { * @param findNow Long-based primary keys to retrieve * @param sids */ - private void lookupPrimaryKeys(final Map acls, final Set findNow, final List sids) { + private void lookupPrimaryKeys(final Map acls, final Set findNow, + final @Nullable List sids) { Assert.notNull(acls, "ACLs are required"); Assert.notEmpty(findNow, "Items to find now required"); String sql = computeRepeatingSql(this.lookupPrimaryKeysWhereClause, findNow.size()); @@ -264,7 +267,7 @@ public class BasicLookupStrategy implements LookupStrategy { * automatically create entries if required) */ @Override - public final Map readAclsById(List objects, List sids) { + public final Map readAclsById(List objects, @Nullable List sids) { Assert.isTrue(this.batchSize >= 1, "BatchSize must be >= 1"); Assert.notEmpty(objects, "Objects to lookup required"); // Map @@ -323,7 +326,7 @@ public class BasicLookupStrategy implements LookupStrategy { * properly-configured parent ACLs. */ private Map lookupObjectIdentities(final Collection objectIdentities, - List sids) { + @Nullable List sids) { Assert.notEmpty(objectIdentities, "Must provide identities to lookup"); // contains Acls with StubAclParents @@ -399,8 +402,10 @@ public class BasicLookupStrategy implements LookupStrategy { } // Now we have the parent (if there is one), create the true AclImpl + Sid owner = inputAcl.getOwner(); + Assert.isTrue(owner != null, "Owner is required"); AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), inputAcl.getId(), this.aclAuthorizationStrategy, - this.grantingStrategy, parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner()); + this.grantingStrategy, parent, null, inputAcl.isEntriesInheriting(), owner); // Copy the "aces" from the input to the destination @@ -506,9 +511,9 @@ public class BasicLookupStrategy implements LookupStrategy { private final Map acls; - private final List sids; + private final @Nullable List sids; - ProcessResultSet(Map acls, List sids) { + ProcessResultSet(Map acls, @Nullable List sids) { Assert.notNull(acls, "ACLs cannot be null"); this.acls = acls; this.sids = sids; // can be null @@ -579,6 +584,9 @@ public class BasicLookupStrategy implements LookupStrategy { // target id type, e.g. UUID. Serializable identifier = (Serializable) rs.getObject("object_id_identity"); identifier = BasicLookupStrategy.this.aclClassIdUtils.identifierFrom(identifier, rs); + if (identifier == null) { + throw new IllegalStateException("Identifier cannot be null"); + } ObjectIdentity objectIdentity = BasicLookupStrategy.this.objectIdentityGenerator .createObjectIdentity(identifier, rs.getString("class")); @@ -670,7 +678,7 @@ public class BasicLookupStrategy implements LookupStrategy { } @Override - public boolean isSidLoaded(List sids) { + public boolean isSidLoaded(@Nullable List sids) { throw new UnsupportedOperationException("Stub only"); } diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java index 69667c8933..da5e57b3fc 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java @@ -27,6 +27,7 @@ import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionService; import org.springframework.jdbc.core.JdbcOperations; @@ -98,7 +99,7 @@ public class JdbcAclService implements AclService { } @Override - public List findChildren(ObjectIdentity parentIdentity) { + public @Nullable List findChildren(ObjectIdentity parentIdentity) { Object[] args = { parentIdentity.getIdentifier().toString(), parentIdentity.getType() }; List objects = this.jdbcOperations.query(this.findChildrenSql, (rs, rowNum) -> mapObjectIdentityRow(rs), args); @@ -109,11 +110,14 @@ public class JdbcAclService implements AclService { String javaType = rs.getString("class"); Serializable identifier = (Serializable) rs.getObject("obj_id"); identifier = this.aclClassIdUtils.identifierFrom(identifier, rs); + if (identifier == null) { + throw new IllegalStateException("Identifier cannot be null"); + } return this.objectIdentityGenerator.createObjectIdentity(identifier, javaType); } @Override - public Acl readAclById(ObjectIdentity object, List sids) throws NotFoundException { + public Acl readAclById(ObjectIdentity object, @Nullable List sids) throws NotFoundException { Map map = readAclsById(Collections.singletonList(object), sids); Assert.isTrue(map.containsKey(object), () -> "There should have been an Acl entry for ObjectIdentity " + object); @@ -131,7 +135,7 @@ public class JdbcAclService implements AclService { } @Override - public Map readAclsById(List objects, List sids) + public Map readAclsById(List objects, @Nullable List sids) throws NotFoundException { Map result = this.lookupStrategy.readAclsById(objects, sids); // Check every requested object identity was found (throw NotFoundException if diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java index 9c22037dd9..c1b5475bed 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java @@ -22,6 +22,8 @@ import java.util.List; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.security.acls.domain.AccessControlEntryImpl; @@ -120,6 +122,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS // Need to retrieve the current principal, in order to know who "owns" this ACL // (can be changed later on) Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication(); + Assert.isTrue(auth != null, "Authentication required"); PrincipalSid sid = new PrincipalSid(auth); // Create the acl_object_identity row @@ -155,9 +158,12 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class"); AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_; + Assert.state(acl.getId() != null, "ACL ID cannot be null"); stmt.setLong(1, (Long) acl.getId()); stmt.setInt(2, i); - stmt.setLong(3, createOrRetrieveSidPrimaryKey(entry.getSid(), true)); + Long sidPrimaryKey = createOrRetrieveSidPrimaryKey(entry.getSid(), true); + Assert.state(sidPrimaryKey != null, "SID primary key cannot be null"); + stmt.setLong(3, sidPrimaryKey); stmt.setInt(4, entry.getPermission().getMask()); stmt.setBoolean(5, entry.isGranting()); stmt.setBoolean(6, entry.isAuditSuccess()); @@ -189,11 +195,14 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS * @param allowCreate true if creation is permitted if not found * @return the primary key or null if not found */ - protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) { - List classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, Long.class, type); + protected @Nullable Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) { + List<@Nullable Long> classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, Long.class, type); if (!classIds.isEmpty()) { - return classIds.get(0); + Long result = classIds.get(0); + if (result != null) { + return result; + } } if (allowCreate) { @@ -204,7 +213,9 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS this.jdbcOperations.update(this.insertClass, type, idType.getCanonicalName()); } Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); - return this.jdbcOperations.queryForObject(this.classIdentityQuery, Long.class); + Long result = this.jdbcOperations.queryForObject(this.classIdentityQuery, Long.class); + Assert.state(result != null, "Failed to retrieve class primary key"); + return result; } return null; @@ -219,7 +230,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS * @throws IllegalArgumentException if the Sid is not a recognized * implementation. */ - protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { + protected @Nullable Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { Assert.notNull(sid, "Sid required"); if (sid instanceof PrincipalSid) { String sidName = ((PrincipalSid) sid).getPrincipal(); @@ -240,16 +251,22 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS * @param allowCreate true if creation is permitted if not found * @return the primary key or null if not found */ - protected Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, boolean allowCreate) { - List sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey, Long.class, sidIsPrincipal, - sidName); + protected @Nullable Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, + boolean allowCreate) { + List<@Nullable Long> sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey, Long.class, + sidIsPrincipal, sidName); if (!sidIds.isEmpty()) { - return sidIds.get(0); + Long result = sidIds.get(0); + if (result != null) { + return result; + } } if (allowCreate) { this.jdbcOperations.update(this.insertSid, sidIsPrincipal, sidName); Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); - return this.jdbcOperations.queryForObject(this.sidIdentityQuery, Long.class); + Long result = this.jdbcOperations.queryForObject(this.sidIdentityQuery, Long.class); + Assert.state(result != null, "Failed to retrieve sid primary key"); + return result; } return null; } @@ -279,6 +296,9 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS } Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity); + if (oidPrimaryKey == null) { + throw new NotFoundException("Object identity not found: " + objectIdentity); + } // Delete this ACL's ACEs in the acl_entry table deleteEntries(oidPrimaryKey); @@ -319,10 +339,11 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS * @param oid to find * @return the object identity or null if not found */ - protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) { + protected @Nullable Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) { try { - return this.jdbcOperations.queryForObject(this.selectObjectIdentityPrimaryKey, Long.class, oid.getType(), - oid.getIdentifier().toString()); + Long result = this.jdbcOperations.queryForObject(this.selectObjectIdentityPrimaryKey, Long.class, + oid.getType(), oid.getIdentifier().toString()); + return result; } catch (DataAccessException notFound) { return null; @@ -340,7 +361,11 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier"); // Delete this ACL's ACEs in the acl_entry table - deleteEntries(retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity())); + Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity()); + if (oidPrimaryKey == null) { + throw new NotFoundException("Object identity not found for ACL: " + acl.getObjectIdentity()); + } + deleteEntries(oidPrimaryKey); // Create this ACL's ACEs in the acl_entry table createEntries(acl); diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java b/acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java index adc6c6aef1..725cfe09d8 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java @@ -19,6 +19,8 @@ package org.springframework.security.acls.jdbc; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; @@ -42,6 +44,6 @@ public interface LookupStrategy { * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to * automatically create entries if required) */ - Map readAclsById(List objects, List sids); + Map readAclsById(List objects, @Nullable List sids); } diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java b/acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java index 35ad8a2867..75bb46cdbc 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java @@ -17,4 +17,7 @@ /** * JDBC-based persistence of ACL information */ +@NullMarked package org.springframework.security.acls.jdbc; + +import org.jspecify.annotations.NullMarked; diff --git a/acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java b/acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java index d8c8e286f6..0ff97844e9 100644 --- a/acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java +++ b/acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java @@ -18,6 +18,8 @@ package org.springframework.security.acls.model; import java.io.Serializable; +import org.jspecify.annotations.Nullable; + /** * Represents an individual permission assignment within an {@link Acl}. * @@ -36,7 +38,7 @@ public interface AccessControlEntry extends Serializable { * Obtains an identifier that represents this ACE. * @return the identifier, or null if unsaved */ - Serializable getId(); + @Nullable Serializable getId(); Permission getPermission(); diff --git a/acl/src/main/java/org/springframework/security/acls/model/Acl.java b/acl/src/main/java/org/springframework/security/acls/model/Acl.java index 8128c9e0c1..06eb71340f 100644 --- a/acl/src/main/java/org/springframework/security/acls/model/Acl.java +++ b/acl/src/main/java/org/springframework/security/acls/model/Acl.java @@ -19,6 +19,8 @@ package org.springframework.security.acls.model; import java.io.Serializable; import java.util.List; +import org.jspecify.annotations.Nullable; + /** * Represents an access control list (ACL) for a domain object. * @@ -82,7 +84,7 @@ public interface Acl extends Serializable { * @return the owner (may be null if the implementation does not use * ownership concepts) */ - Sid getOwner(); + @Nullable Sid getOwner(); /** * A domain object may have a parent for the purpose of ACL inheritance. If there is a @@ -103,7 +105,7 @@ public interface Acl extends Serializable { * @return the parent Acl (may be null if this Acl does not * have a parent) */ - Acl getParentAcl(); + @Nullable Acl getParentAcl(); /** * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down @@ -189,6 +191,6 @@ public interface Acl extends Serializable { * @return true if every passed Sid is represented by this * Acl instance */ - boolean isSidLoaded(List sids); + boolean isSidLoaded(@Nullable List sids); } diff --git a/acl/src/main/java/org/springframework/security/acls/model/AclCache.java b/acl/src/main/java/org/springframework/security/acls/model/AclCache.java index 945b1b1a2a..70ea846c5d 100644 --- a/acl/src/main/java/org/springframework/security/acls/model/AclCache.java +++ b/acl/src/main/java/org/springframework/security/acls/model/AclCache.java @@ -18,6 +18,8 @@ package org.springframework.security.acls.model; import java.io.Serializable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.acls.jdbc.JdbcAclService; /** @@ -31,9 +33,9 @@ public interface AclCache { void evictFromCache(ObjectIdentity objectIdentity); - MutableAcl getFromCache(ObjectIdentity objectIdentity); + @Nullable MutableAcl getFromCache(ObjectIdentity objectIdentity); - MutableAcl getFromCache(Serializable pk); + @Nullable MutableAcl getFromCache(Serializable pk); void putInCache(MutableAcl acl); diff --git a/acl/src/main/java/org/springframework/security/acls/model/AclService.java b/acl/src/main/java/org/springframework/security/acls/model/AclService.java index 2866ec83bd..92c243a524 100644 --- a/acl/src/main/java/org/springframework/security/acls/model/AclService.java +++ b/acl/src/main/java/org/springframework/security/acls/model/AclService.java @@ -19,6 +19,8 @@ package org.springframework.security.acls.model; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + /** * Provides retrieval of {@link Acl} instances. * @@ -32,7 +34,7 @@ public interface AclService { * @param parentIdentity to locate children of * @return the children (or null if none were found) */ - List findChildren(ObjectIdentity parentIdentity); + @Nullable List findChildren(ObjectIdentity parentIdentity); /** * Same as {@link #readAclsById(List)} except it returns only a single Acl. @@ -59,7 +61,7 @@ public interface AclService { * @throws NotFoundException if an {@link Acl} was not found for the requested * {@link ObjectIdentity} */ - Acl readAclById(ObjectIdentity object, List sids) throws NotFoundException; + Acl readAclById(ObjectIdentity object, @Nullable List sids) throws NotFoundException; /** * Obtains all the Acls that apply for the passed Objects. @@ -98,6 +100,7 @@ public interface AclService { * @throws NotFoundException if an {@link Acl} was not found for each requested * {@link ObjectIdentity} */ - Map readAclsById(List objects, List sids) throws NotFoundException; + Map readAclsById(List objects, @Nullable List sids) + throws NotFoundException; } diff --git a/acl/src/main/java/org/springframework/security/acls/model/package-info.java b/acl/src/main/java/org/springframework/security/acls/model/package-info.java index 36967fc44f..4d1a02b8b3 100644 --- a/acl/src/main/java/org/springframework/security/acls/model/package-info.java +++ b/acl/src/main/java/org/springframework/security/acls/model/package-info.java @@ -18,4 +18,7 @@ * Interfaces and shared classes to manage access control lists (ACLs) for domain object * instances. */ +@NullMarked package org.springframework.security.acls.model; + +import org.jspecify.annotations.NullMarked; diff --git a/acl/src/main/java/org/springframework/security/acls/package-info.java b/acl/src/main/java/org/springframework/security/acls/package-info.java index e137f16081..f0cbdc1778 100644 --- a/acl/src/main/java/org/springframework/security/acls/package-info.java +++ b/acl/src/main/java/org/springframework/security/acls/package-info.java @@ -24,4 +24,7 @@ * older and more verbose attribute/voter/after-invocation approach from versions before * Spring Security 3.0. */ +@NullMarked package org.springframework.security.acls; + +import org.jspecify.annotations.NullMarked;