All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.acegisecurity.acls.jdbc.BasicLookupStrategy Maven / Gradle / Ivy

The newest version!
/* Copyright 2004, 2005, 2006 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.
 * 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.acegisecurity.acls.jdbc;

import org.acegisecurity.acls.AccessControlEntry;
import org.acegisecurity.acls.Acl;
import org.acegisecurity.acls.MutableAcl;
import org.acegisecurity.acls.NotFoundException;
import org.acegisecurity.acls.Permission;
import org.acegisecurity.acls.UnloadedSidException;
import org.acegisecurity.acls.domain.AccessControlEntryImpl;
import org.acegisecurity.acls.domain.AclAuthorizationStrategy;
import org.acegisecurity.acls.domain.AclImpl;
import org.acegisecurity.acls.domain.AuditLogger;
import org.acegisecurity.acls.domain.BasePermission;
import org.acegisecurity.acls.objectidentity.ObjectIdentity;
import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
import org.acegisecurity.acls.sid.GrantedAuthoritySid;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.acegisecurity.acls.sid.Sid;

import org.acegisecurity.util.FieldUtils;

import org.springframework.dao.DataAccessException;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;

import org.springframework.util.Assert;

import java.lang.reflect.Field;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;


/**
 * Performs lookups in a manner that is compatible with ANSI SQL.

NB: This implementation does attempt to provide * reasonably optimised lookups - within the constraints of a normalised database and standard ANSI SQL features. If * you are willing to sacrifice either of these constraints (eg use a particular database feature such as hierarchical * queries or materalized views, or reduce normalisation) you are likely to achieve better performance. In such * situations you will need to provide your own custom LookupStrategy. This class does not support * subclassing, as it is likely to change in future releases and therefore subclassing is unsupported.

* * @author Ben Alex * @version $Id: BasicLookupStrategy.java 1784 2007-02-24 21:00:24Z luke_t $ */ public final class BasicLookupStrategy implements LookupStrategy { //~ Instance fields ================================================================================================ private AclAuthorizationStrategy aclAuthorizationStrategy; private AclCache aclCache; private AuditLogger auditLogger; private JdbcTemplate jdbcTemplate; private int batchSize = 50; //~ Constructors =================================================================================================== /** * Constructor accepting mandatory arguments * * @param dataSource to access the database * @param aclCache the cache where fully-loaded elements can be stored * @param aclAuthorizationStrategy authorization strategy (required) */ public BasicLookupStrategy(DataSource dataSource, AclCache aclCache, AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) { Assert.notNull(dataSource, "DataSource required"); Assert.notNull(aclCache, "AclCache required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); Assert.notNull(auditLogger, "AuditLogger required"); this.jdbcTemplate = new JdbcTemplate(dataSource); this.aclCache = aclCache; this.aclAuthorizationStrategy = aclAuthorizationStrategy; this.auditLogger = auditLogger; } //~ Methods ======================================================================================================== private static String computeRepeatingSql(String repeatingSql, int requiredRepetitions) { Assert.isTrue(requiredRepetitions >= 1, "Must be => 1"); String startSql = "select ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY, ACL_ENTRY.ACE_ORDER, " + "ACL_OBJECT_IDENTITY.ID as ACL_ID, " + "ACL_OBJECT_IDENTITY.PARENT_OBJECT, " + "ACL_OBJECT_IDENTITY,ENTRIES_INHERITING, " + "ACL_ENTRY.ID as ACE_ID, ACL_ENTRY.MASK, ACL_ENTRY.GRANTING, " + "ACL_ENTRY.AUDIT_SUCCESS, ACL_ENTRY.AUDIT_FAILURE, " + "ACL_SID.PRINCIPAL as ACE_PRINCIPAL, 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_SID ACLI_SID, ACL_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 ACLI_SID.ID = ACL_OBJECT_IDENTITY.OWNER_SID " + "and ACL_CLASS.ID = ACL_OBJECT_IDENTITY.OBJECT_ID_CLASS " + "and ( "; String endSql = ") order by ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc"; StringBuffer sqlStringBuffer = new StringBuffer(); sqlStringBuffer.append(startSql); for (int i = 1; i <= requiredRepetitions; i++) { sqlStringBuffer.append(repeatingSql); if (i != requiredRepetitions) { sqlStringBuffer.append(" or "); } } sqlStringBuffer.append(endSql); return sqlStringBuffer.toString(); } /** * The final phase of converting the Map of AclImpl instances which contain * StubAclParents into proper, valid AclImpls with correct ACL parents. * * @param inputMap the unconverted AclImpls * @param currentIdentity the currentAcl that we wish to convert (this may be * * @return * * @throws IllegalStateException DOCUMENT ME! */ private AclImpl convert(Map inputMap, Long currentIdentity) { Assert.notEmpty(inputMap, "InputMap required"); Assert.notNull(currentIdentity, "CurrentIdentity required"); // Retrieve this Acl from the InputMap Acl uncastAcl = (Acl) inputMap.get(currentIdentity); Assert.isInstanceOf(AclImpl.class, uncastAcl, "The inputMap contained a non-AclImpl"); AclImpl inputAcl = (AclImpl) uncastAcl; Acl parent = inputAcl.getParentAcl(); if ((parent != null) && parent instanceof StubAclParent) { // Lookup the parent StubAclParent stubAclParent = (StubAclParent) parent; parent = convert(inputMap, stubAclParent.getId()); } // Now we have the parent (if there is one), create the true AclImpl AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), (Long) inputAcl.getId(), aclAuthorizationStrategy, auditLogger, parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner()); // Copy the "aces" from the input to the destination Field field = FieldUtils.getField(AclImpl.class, "aces"); try { field.setAccessible(true); field.set(result, field.get(inputAcl)); } catch (IllegalAccessException ex) { throw new IllegalStateException("Could not obtain or set AclImpl.ace field"); } return result; } /** * Accepts the current ResultSet row, and converts it into an AclImpl that * contains a StubAclParent * * @param acls the Map we should add the converted Acl to * @param rs the ResultSet focused on a current row * * @throws SQLException if something goes wrong converting values * @throws IllegalStateException DOCUMENT ME! */ private void convertCurrentResultIntoObject(Map acls, ResultSet rs) throws SQLException { Long id = new Long(rs.getLong("ACL_ID")); // If we already have an ACL for this ID, just create the ACE AclImpl acl = (AclImpl) acls.get(id); if (acl == null) { // Make an AclImpl and pop it into the Map ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("CLASS"), new Long(rs.getLong("OBJECT_ID_IDENTITY"))); Acl parentAcl = null; long parentAclId = rs.getLong("PARENT_OBJECT"); if (parentAclId != 0) { parentAcl = new StubAclParent(new Long(parentAclId)); } boolean entriesInheriting = rs.getBoolean("ENTRIES_INHERITING"); Sid owner; if (rs.getBoolean("ACL_PRINCIPAL")) { owner = new PrincipalSid(rs.getString("ACL_SID")); } else { owner = new GrantedAuthoritySid(rs.getString("ACL_SID")); } acl = new AclImpl(objectIdentity, id, aclAuthorizationStrategy, auditLogger, parentAcl, null, entriesInheriting, owner); acls.put(id, acl); } // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order) // It is permissable to have no ACEs in an ACL (which is detected by a null ACE_SID) if (rs.getString("ACE_SID") != null) { Long aceId = new Long(rs.getLong("ACE_ID")); Sid recipient; if (rs.getBoolean("ACE_PRINCIPAL")) { recipient = new PrincipalSid(rs.getString("ACE_SID")); } else { recipient = new GrantedAuthoritySid(rs.getString("ACE_SID")); } Permission permission = BasePermission.buildFromMask(rs.getInt("MASK")); boolean granting = rs.getBoolean("GRANTING"); boolean auditSuccess = rs.getBoolean("AUDIT_SUCCESS"); boolean auditFailure = rs.getBoolean("AUDIT_FAILURE"); AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting, auditSuccess, auditFailure); Field acesField = FieldUtils.getField(AclImpl.class, "aces"); List aces; try { acesField.setAccessible(true); aces = (List) acesField.get(acl); } catch (IllegalAccessException ex) { throw new IllegalStateException("Could not obtain AclImpl.ace field: cause[" + ex.getMessage() + "]"); } // Add the ACE if it doesn't already exist in the ACL.aces field if (!aces.contains(ace)) { aces.add(ace); } } } /** * Looks up a batch of ObjectIdentitys directly from the database.

The caller is responsible * for optimization issues, such as selecting the identities to lookup, ensuring the cache doesn't contain them * already, and adding the returned elements to the cache etc.

*

This subclass is required to return fully valid Acls, including properly-configured * parent ACLs.

* * @param objectIdentities DOCUMENT ME! * @param sids DOCUMENT ME! * * @return DOCUMENT ME! */ private Map lookupObjectIdentities(final ObjectIdentity[] objectIdentities, Sid[] sids) { Assert.notEmpty(objectIdentities, "Must provide identities to lookup"); final Map acls = new HashMap(); // contains Acls with StubAclParents // Make the "acls" map contain all requested objectIdentities // (including markers to each parent in the hierarchy) String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = ? and ACL_CLASS.CLASS = ?)", objectIdentities.length); jdbcTemplate.query(sql, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { for (int i = 0; i < objectIdentities.length; i++) { // Determine prepared statement values for this iteration String javaType = objectIdentities[i].getJavaType().getName(); Assert.isInstanceOf(Long.class, objectIdentities[i].getIdentifier(), "This class requires ObjectIdentity.getIdentifier() to be a Long"); long id = ((Long) objectIdentities[i].getIdentifier()).longValue(); // Inject values ps.setLong((2 * i) + 1, id); ps.setString((2 * i) + 2, javaType); } } }, new ProcessResultSet(acls, sids)); // Finally, convert our "acls" containing StubAclParents into true Acls Map resultMap = new HashMap(); Iterator iter = acls.values().iterator(); while (iter.hasNext()) { Acl inputAcl = (Acl) iter.next(); Assert.isInstanceOf(AclImpl.class, inputAcl, "Map should have contained an AclImpl"); Assert.isInstanceOf(Long.class, ((AclImpl) inputAcl).getId(), "Acl.getId() must be Long"); Acl result = convert(acls, (Long) ((AclImpl) inputAcl).getId()); resultMap.put(result.getObjectIdentity(), result); } return resultMap; } /** * Locates the primary key IDs specified in "findNow", adding AclImpl instances with StubAclParents to the * "acls" Map. * * @param acls the AclImpls (with StubAclParents) * @param findNow Long-based primary keys to retrieve * @param sids DOCUMENT ME! */ private void lookupPrimaryKeys(final Map acls, final Set findNow, final Sid[] sids) { Assert.notNull(acls, "ACLs are required"); Assert.notEmpty(findNow, "Items to find now required"); String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.ID = ?)", findNow.size()); jdbcTemplate.query(sql, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { Iterator iter = findNow.iterator(); int i = 0; while (iter.hasNext()) { i++; ps.setLong(i, ((Long) iter.next()).longValue()); } } }, new ProcessResultSet(acls, sids)); } /** * The main method.

WARNING: This implementation completely disregards the "sids" parameter! Every item * in the cache is expected to contain all SIDs. If you have serious performance needs (eg a very large number of * SIDs per object identity), you'll probably want to develop a custom {@link LookupStrategy} implementation * instead.

*

The implementation works in batch sizes specfied by {@link #batchSize}.

* * @param objects DOCUMENT ME! * @param sids DOCUMENT ME! * * @return DOCUMENT ME! * * @throws NotFoundException DOCUMENT ME! * @throws IllegalStateException DOCUMENT ME! */ public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException { Assert.isTrue(batchSize >= 1, "BatchSize must be >= 1"); Assert.notEmpty(objects, "Objects to lookup required"); // Map Map result = new HashMap(); // contains FULLY loaded Acl objects Set currentBatchToLoad = new HashSet(); // contains ObjectIdentitys for (int i = 0; i < objects.length; i++) { // Check we don't already have this ACL in the results if (result.containsKey(objects[i])) { continue; // already in results, so move to next element } // Check cache for the present ACL entry Acl acl = aclCache.getFromCache(objects[i]); // Ensure any cached element supports all the requested SIDs // (they should always, as our base impl doesn't filter on SID) if (acl != null) { if (acl.isSidLoaded(sids)) { result.put(acl.getObjectIdentity(), acl); continue; // now in results, so move to next element } else { throw new IllegalStateException( "Error: SID-filtered element detected when implementation does not perform SID filtering " + "- have you added something to the cache manually?"); } } // To get this far, we have no choice but to retrieve it via JDBC // (although we don't do it until we get a batch of them to load) currentBatchToLoad.add(objects[i]); // Is it time to load from JDBC the currentBatchToLoad? if ((currentBatchToLoad.size() == this.batchSize) || ((i + 1) == objects.length)) { Map loadedBatch = lookupObjectIdentities((ObjectIdentity[]) currentBatchToLoad.toArray( new ObjectIdentity[] {}), sids); // Add loaded batch (all elements 100% initialized) to results result.putAll(loadedBatch); // Add the loaded batch to the cache Iterator loadedAclIterator = loadedBatch.values().iterator(); while (loadedAclIterator.hasNext()) { aclCache.putInCache((AclImpl) loadedAclIterator.next()); } currentBatchToLoad.clear(); } } // Now we're done, check every requested object identity was found (throw NotFoundException if needed) for (int i = 0; i < objects.length; i++) { if (!result.containsKey(objects[i])) { throw new NotFoundException("Unable to find ACL information for object identity '" + objects[i].toString() + "'"); } } return result; } public void setBatchSize(int batchSize) { this.batchSize = batchSize; } //~ Inner Classes ================================================================================================== private class ProcessResultSet implements ResultSetExtractor { private Map acls; private Sid[] sids; public ProcessResultSet(Map acls, Sid[] sids) { Assert.notNull(acls, "ACLs cannot be null"); this.acls = acls; this.sids = sids; // can be null } public Object extractData(ResultSet rs) throws SQLException, DataAccessException { Set parentIdsToLookup = new HashSet(); // Set of parent_id Longs while (rs.next()) { // Convert current row into an Acl (albeit with a StubAclParent) convertCurrentResultIntoObject(acls, rs); // Figure out if this row means we need to lookup another parent long parentId = rs.getLong("PARENT_OBJECT"); if (parentId != 0) { // See if it's already in the "acls" if (acls.containsKey(new Long(parentId))) { continue; // skip this while iteration } // Now try to find it in the cache MutableAcl cached = aclCache.getFromCache(new Long(parentId)); if ((cached == null) || !cached.isSidLoaded(sids)) { parentIdsToLookup.add(new Long(parentId)); } else { // Pop into the acls map, so our convert method doesn't // need to deal with an unsynchronized AclCache acls.put(cached.getId(), cached); } } } // Lookup parents, adding Acls (with StubAclParents) to "acl" map if (parentIdsToLookup.size() > 0) { lookupPrimaryKeys(acls, parentIdsToLookup, sids); } // Return null to meet ResultSetExtractor method contract return null; } } private class StubAclParent implements Acl { private Long id; public StubAclParent(Long id) { this.id = id; } public AccessControlEntry[] getEntries() { throw new UnsupportedOperationException("Stub only"); } public Long getId() { return id; } public ObjectIdentity getObjectIdentity() { throw new UnsupportedOperationException("Stub only"); } public Sid getOwner() { throw new UnsupportedOperationException("Stub only"); } public Acl getParentAcl() { throw new UnsupportedOperationException("Stub only"); } public boolean isEntriesInheriting() { throw new UnsupportedOperationException("Stub only"); } public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode) throws NotFoundException, UnloadedSidException { throw new UnsupportedOperationException("Stub only"); } public boolean isSidLoaded(Sid[] sids) { throw new UnsupportedOperationException("Stub only"); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy