1
0
mirror of synced 2026-08-04 17:27:13 +00:00

SEC-674: Created new project modules for cas, captcha, acls and taglibs

This commit is contained in:
Luke Taylor
2008-02-19 20:30:53 +00:00
parent 59651f5214
commit 2dd9faabc0
149 changed files with 425 additions and 218 deletions
@@ -1,57 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.acls.sid.Sid;
import java.io.Serializable;
/**
* Represents an individual permission assignment within an {@link Acl}.
*
* <p>
* Instances MUST be immutable, as they are returned by <code>Acl</code>
* and should not allow client modification.
* </p>
*
* @author Ben Alex
* @version $Id$
*
*/
public interface AccessControlEntry {
//~ Methods ========================================================================================================
Acl getAcl();
/**
* Obtains an identifier that represents this ACE.
*
* @return the identifier, or <code>null</code> if unsaved
*/
Serializable getId();
Permission getPermission();
Sid getSid();
/**
* Indicates the a Permission is being granted to the relevant Sid. If false, indicates the permission is
* being revoked/blocked.
*
* @return true if being granted, false otherwise
*/
boolean isGranting();
}
@@ -1,155 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.sid.Sid;
import java.io.Serializable;
/**
* Represents an access control list (ACL) for a domain object.
*
* <p>
* An <code>Acl</code> represents all ACL entries for a given domain object. In
* order to avoid needing references to the domain object itself, this
* interface handles indirection between a domain object and an ACL object
* identity via the {@link
* org.springframework.security.acls.objectidentity.ObjectIdentity} interface.
* </p>
*
* <p>
* An implementation represents the {@link org.springframework.security.acls.Permission}
* list applicable for some or all {@link org.springframework.security.acls.sid.Sid}
* instances.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface Acl extends Serializable {
//~ Methods ========================================================================================================
/**
* Returns all of the entries represented by the present <code>Acl</code> (not parents).<p>This method is
* typically used for administrative purposes.</p>
* <p>The order that entries appear in the array is unspecified. However, if implementations use
* particular ordering logic in authorization decisions, the entries returned by this method <em>MUST</em> be
* ordered in that manner.</p>
* <p>Do <em>NOT</em> use this method for making authorization decisions. Instead use {@link
* #isGranted(Permission[], Sid[], boolean)}.</p>
* <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
* <code>Sid</code>s. The caller is responsible for correctly handling the result if only a subset of
* <code>Sid</code>s is represented.</p>
*
* @return the list of entries represented by the <code>Acl</code>
*/
AccessControlEntry[] getEntries();
/**
* Obtains the domain object this <code>Acl</code> provides entries for. This is immutable once an
* <code>Acl</code> is created.
*
* @return the object identity
*/
ObjectIdentity getObjectIdentity();
/**
* Determines the owner of the <code>Acl</code>. The meaning of ownership varies by implementation and is
* unspecified.
*
* @return the owner (may be null if the implementation does not use ownership concepts)
*/
Sid getOwner();
/**
* A domain object may have a parent for the purpose of ACL inheritance. If there is a parent, its ACL can
* be accessed via this method. In turn, the parent's parent (grandparent) can be accessed and so on.<p>This
* method solely represents the presence of a navigation hierarchy between the parent <code>Acl</code> and this
* <code>Acl</code>. For actual inheritance to take place, the {@link #isEntriesInheriting()} must also be
* <code>true</code>.</p>
* <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
* <code>Sid</code>s. The caller is responsible for correctly handling the result if only a subset of
* <code>Sid</code>s is represented.</p>
*
* @return the parent <code>Acl</code>
*/
Acl getParentAcl();
/**
* Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down into the current
* <code>Acl</code>.<p>The mere link between an <code>Acl</code> and a parent <code>Acl</code> on its own
* is insufficient to cause ACL entries to inherit down. This is because a domain object may wish to have entirely
* independent entries, but maintain the link with the parent for navigation purposes. Thus, this method denotes
* whether or not the navigation relationship also extends to the actual inheritence of entries.</p>
*
* @return <code>true</code> if parent ACL entries inherit into the current <code>Acl</code>
*/
boolean isEntriesInheriting();
/**
* This is the actual authorization logic method, and must be used whenever ACL authorization decisions are
* required.<p>An array of <code>Sid</code>s are presented, representing security identifies of the current
* principal. In addition, an array of <code>Permission</code>s is presented which will have one or more bits set
* in order to indicate the permissions needed for an affirmative authorization decision. An array is presented
* because holding <em>any</em> of the <code>Permission</code>s inside the array will be sufficient for an
* affirmative authorization.</p>
* <p>The actual approach used to make authorization decisions is left to the implementation and is not
* specified by this interface. For example, an implementation <em>MAY</em> search the current ACL in the order
* the ACL entries have been stored. If a single entry is found that has the same active bits as are shown in a
* passed <code>Permission</code>, that entry's grant or deny state may determine the authorization decision. If
* the case of a deny state, the deny decision will only be relevant if all other <code>Permission</code>s passed
* in the array have also been unsuccessfully searched. If no entry is found that match the bits in the current
* ACL, provided that {@link #isEntriesInheriting()} is <code>true</code>, the authorization decision may be
* passed to the parent ACL. If there is no matching entry, the implementation MAY throw an exception, or make a
* predefined authorization decision.</p>
* <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
* <code>Sid</code>s.</p>
*
* @param permission the permission or permissions required
* @param sids the security identities held by the principal
* @param administrativeMode if <code>true</code> denotes the query is for administrative purposes and no logging
* or auditing (if supported by the implementation) should be undertaken
*
* @return <code>true</code> is authorization is granted
*
* @throws NotFoundException MUST be thrown if an implementation cannot make an authoritative authorization
* decision, usually because there is no ACL information for this particular permission and/or SID
* @throws UnloadedSidException thrown if the <code>Acl</code> does not have details for one or more of the
* <code>Sid</code>s passed as arguments
*/
boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
throws NotFoundException, UnloadedSidException;
/**
* For efficiency reasons an <code>Acl</code> may be loaded and <em>not</em> contain entries for every
* <code>Sid</code> in the system. If an <code>Acl</code> has been loaded and does not represent every
* <code>Sid</code>, all methods of the <code>Sid</code> can only be used within the limited scope of the
* <code>Sid</code> instances it actually represents.
* <p>
* It is normal to load an <code>Acl</code> for only particular <code>Sid</code>s if read-only authorization
* decisions are being made. However, if user interface reporting or modification of <code>Acl</code>s are
* desired, an <code>Acl</code> should be loaded with all <code>Sid</code>s. This method denotes whether or
* not the specified <code>Sid</code>s have been loaded or not.
* </p>
*
* @param sids one or more security identities the caller is interest in knowing whether this <code>Sid</code>
* supports
*
* @return <code>true</code> if every passed <code>Sid</code> is represented by this <code>Acl</code> instance
*/
boolean isSidLoaded(Sid[] sids);
}
@@ -1,111 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.util.Assert;
/**
* Utility methods for displaying ACL information.
*
* @author Ben Alex
* @version $Id$
*/
public final class AclFormattingUtils {
//~ Constructors ===================================================================================================
private AclFormattingUtils() {
}
//~ Methods ========================================================================================================
public static String demergePatterns(String original, String removeBits) {
Assert.notNull(original, "Original string required");
Assert.notNull(removeBits, "Bits To Remove string required");
Assert.isTrue(original.length() == removeBits.length(),
"Original and Bits To Remove strings must be identical length");
char[] replacement = new char[original.length()];
for (int i = 0; i < original.length(); i++) {
if (removeBits.charAt(i) == Permission.RESERVED_OFF) {
replacement[i] = original.charAt(i);
} else {
replacement[i] = Permission.RESERVED_OFF;
}
}
return new String(replacement);
}
public static String mergePatterns(String original, String extraBits) {
Assert.notNull(original, "Original string required");
Assert.notNull(extraBits, "Extra Bits string required");
Assert.isTrue(original.length() == extraBits.length(),
"Original and Extra Bits strings must be identical length");
char[] replacement = new char[extraBits.length()];
for (int i = 0; i < extraBits.length(); i++) {
if (extraBits.charAt(i) == Permission.RESERVED_OFF) {
replacement[i] = original.charAt(i);
} else {
replacement[i] = extraBits.charAt(i);
}
}
return new String(replacement);
}
private static String printBinary(int i, char on, char off) {
String s = Integer.toString(i, 2);
String pattern = Permission.THIRTY_TWO_RESERVED_OFF;
String temp2 = pattern.substring(0, pattern.length() - s.length()) + s;
return temp2.replace('0', off).replace('1', on);
}
/**
* Returns a representation of the active bits in the presented mask, with each active bit being denoted by
* character "".<p>Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.</p>
*
* @param i the integer bit mask to print the active bits for
*
* @return a 32-character representation of the bit mask
*/
public static String printBinary(int i) {
return printBinary(i, '*', Permission.RESERVED_OFF);
}
/**
* Returns a representation of the active bits in the presented mask, with each active bit being denoted by
* the passed character.
* <p>
* Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.
*
* @param mask the integer bit mask to print the active bits for
* @param code the character to print when an active bit is detected
*
* @return a 32-character representation of the bit mask
*/
public static String printBinary(int mask, char code) {
Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_ON),
Permission.RESERVED_ON + " is a reserved character code");
Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_OFF),
Permission.RESERVED_OFF + " is a reserved character code");
return printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF).replace(Permission.RESERVED_ON, code);
}
}
@@ -1,100 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.sid.Sid;
import java.util.Map;
/**
* Provides retrieval of {@link Acl} instances.
*
* @author Ben Alex
* @version $Id$
*/
public interface AclService {
//~ Methods ========================================================================================================
/**
* Locates all object identities that use the specified parent. This is useful for administration tools.
*
* @param parentIdentity to locate children of
*
* @return the children (or <code>null</code> if none were found)
*/
ObjectIdentity[] findChildren(ObjectIdentity parentIdentity);
/**
* Same as {@link #readAclsById(ObjectIdentity[])} except it returns only a single Acl.<p>This method
* should not be called as it does not leverage the underlaying implementation's potential ability to filter
* <code>Acl</code> entries based on a {@link Sid} parameter.</p>
*
* @param object DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws NotFoundException DOCUMENT ME!
*/
Acl readAclById(ObjectIdentity object) throws NotFoundException;
/**
* Same as {@link #readAclsById(ObjectIdentity[], Sid[])} except it returns only a single Acl.
*
* @param object DOCUMENT ME!
* @param sids DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws NotFoundException DOCUMENT ME!
*/
Acl readAclById(ObjectIdentity object, Sid[] sids)
throws NotFoundException;
/**
* Obtains all the <code>Acl</code>s that apply for the passed <code>Object</code>s.<p>The returned map is
* keyed on the passed objects, with the values being the <code>Acl</code> instances. Any unknown objects will not
* have a map key.</p>
*
* @param objects the objects to find ACL information for
*
* @return a map with zero or more elements (never <code>null</code>)
*
* @throws NotFoundException DOCUMENT ME!
*/
Map readAclsById(ObjectIdentity[] objects) throws NotFoundException;
/**
* Obtains all the <code>Acl</code>s that apply for the passed <code>Object</code>s, but only for the
* security identifies passed.<p>Implementations <em>MAY</em> provide a subset of the ACLs via this method
* although this is NOT a requirement. This is intended to allow performance optimisations within implementations.
* Callers should therefore use this method in preference to the alternative overloaded version which does not
* have performance optimisation opportunities.</p>
* <p>The returned map is keyed on the passed objects, with the values being the <code>Acl</code>
* instances. Any unknown objects (or objects for which the interested <code>Sid</code>s do not have entries) will
* not have a map key.</p>
*
* @param objects the objects to find ACL information for
* @param sids the security identities for which ACL information is required (may be <code>null</code> to denote
* all entries)
*
* @return a map with zero or more elements (never <code>null</code>)
*
* @throws NotFoundException DOCUMENT ME!
*/
Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
throws NotFoundException;
}
@@ -1,48 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.SpringSecurityException;
/**
* Thrown if an <code>Acl</code> entry already exists for the object.
*
* @author Ben Alex
* @version $Id$
*/
public class AlreadyExistsException extends SpringSecurityException {
//~ Constructors ===================================================================================================
/**
* Constructs an <code>AlreadyExistsException</code> with the specified message.
*
* @param msg the detail message
*/
public AlreadyExistsException(String msg) {
super(msg);
}
/**
* Constructs an <code>AlreadyExistsException</code> with the specified message
* and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public AlreadyExistsException(String msg, Throwable t) {
super(msg, t);
}
}
@@ -1,30 +0,0 @@
/* 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.springframework.security.acls;
/**
* Represents an ACE that provides auditing information.
*
* @author Ben Alex
* @version $Id$
*
*/
public interface AuditableAccessControlEntry extends AccessControlEntry {
//~ Methods ========================================================================================================
boolean isAuditFailure();
boolean isAuditSuccess();
}
@@ -1,31 +0,0 @@
/* 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.springframework.security.acls;
import java.io.Serializable;
/**
* A mutable ACL that provides audit capabilities.
*
* @author Ben Alex
* @version $Id$
*
*/
public interface AuditableAcl extends MutableAcl {
//~ Methods ========================================================================================================
void updateAuditing(Serializable aceId, boolean auditSuccess, boolean auditFailure);
}
@@ -1,49 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.SpringSecurityException;
/**
* Thrown if an {@link Acl} cannot be deleted because children <code>Acl</code>s exist.
*
* @author Ben Alex
* @version $Id$
*/
public class ChildrenExistException extends SpringSecurityException {
//~ Constructors ===================================================================================================
/**
* Constructs an <code>ChildrenExistException</code> with the specified
* message.
*
* @param msg the detail message
*/
public ChildrenExistException(String msg) {
super(msg);
}
/**
* Constructs an <code>ChildrenExistException</code> with the specified
* message and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public ChildrenExistException(String msg, Throwable t) {
super(msg, t);
}
}
@@ -1,48 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.SpringSecurityException;
/**
* Thrown if an ACL identity could not be extracted from an object.
*
* @author Ben Alex
* @version $Id$
*/
public class IdentityUnavailableException extends SpringSecurityException {
//~ Constructors ===================================================================================================
/**
* Constructs an <code>IdentityUnavailableException</code> with the specified message.
*
* @param msg the detail message
*/
public IdentityUnavailableException(String msg) {
super(msg);
}
/**
* Constructs an <code>IdentityUnavailableException</code> with the specified message
* and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public IdentityUnavailableException(String msg, Throwable t) {
super(msg, t);
}
}
@@ -1,75 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.acls.sid.Sid;
import java.io.Serializable;
/**
* A mutable <code>Acl</code>.
*
* <p>
* A mutable ACL must ensure that appropriate security checks are performed
* before allowing access to its methods.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface MutableAcl extends Acl {
//~ Methods ========================================================================================================
void deleteAce(Serializable aceId) throws NotFoundException;
/**
* Retrieves all of the non-deleted {@link AccessControlEntry} instances currently stored by the
* <code>MutableAcl</code>. The returned objects should be immutable outside the package, and therefore it is safe
* to return them to the caller for informational purposes. The <code>AccessControlEntry</code> information is
* needed so that invocations of update and delete methods on the <code>MutableAcl</code> can refer to a valid
* {@link AccessControlEntry#getId()}.
*
* @return DOCUMENT ME!
*/
AccessControlEntry[] getEntries();
/**
* Obtains an identifier that represents this <code>MutableAcl</code>.
*
* @return the identifier, or <code>null</code> if unsaved
*/
Serializable getId();
void insertAce(Serializable afterAceId, Permission permission, Sid sid, boolean granting)
throws NotFoundException;
/**
* Change the value returned by {@link Acl#isEntriesInheriting()}.
*
* @param entriesInheriting the new value
*/
void setEntriesInheriting(boolean entriesInheriting);
/**
* Changes the parent of this ACL.
*
* @param newParent the new parent
*/
void setParent(Acl newParent);
void updateAce(Serializable aceId, Permission permission)
throws NotFoundException;
}
@@ -1,65 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
/**
* Provides support for creating and storing <code>Acl</code> instances.
*
* @author Ben Alex
* @version $Id$
*/
public interface MutableAclService extends AclService {
//~ Methods ========================================================================================================
/**
* Creates an empty <code>Acl</code> object in the database. It will have no entries. The returned object
* will then be used to add entries.
*
* @param objectIdentity the object identity to create
*
* @return an ACL object with its ID set
*
* @throws AlreadyExistsException if the passed object identity already has a record
*/
MutableAcl createAcl(ObjectIdentity objectIdentity)
throws AlreadyExistsException;
/**
* Removes the specified entry from the database.
*
* @param objectIdentity the object identity to remove
* @param deleteChildren whether to cascade the delete to children
*
* @throws ChildrenExistException if the deleteChildren argument was <code>false</code> but children exist
*/
void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
throws ChildrenExistException;
/**
* Changes an existing <code>Acl</code> in the database.
*
* @param acl to modify
*
* @return DOCUMENT ME!
*
* @throws NotFoundException if the relevant record could not be found (did you remember to use {@link
* #createAcl(ObjectIdentity)} to create the object, rather than creating it with the <code>new</code>
* keyword?)
*/
MutableAcl updateAcl(MutableAcl acl) throws NotFoundException;
}
@@ -1,48 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.SpringSecurityException;
/**
* Thrown if an ACL-related object cannot be found.
*
* @author Ben Alex
* @version $Id$
*/
public class NotFoundException extends SpringSecurityException {
//~ Constructors ===================================================================================================
/**
* Constructs an <code>NotFoundException</code> with the specified message.
*
* @param msg the detail message
*/
public NotFoundException(String msg) {
super(msg);
}
/**
* Constructs an <code>NotFoundException</code> with the specified message
* and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public NotFoundException(String msg, Throwable t) {
super(msg, t);
}
}
@@ -1,35 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.acls.sid.Sid;
/**
* A mutable ACL that provides ownership capabilities.
*
* <p>
* Generally the owner of an ACL is able to call any ACL mutator method, as
* well as assign a new owner.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface OwnershipAcl extends MutableAcl {
//~ Methods ========================================================================================================
void setOwner(Sid newOwner);
}
@@ -1,55 +0,0 @@
/* 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.springframework.security.acls;
/**
* Represents a permission granted to a {@link org.springframework.security.acls.sid.Sid Sid} for a given domain object.
*
* @author Ben Alex
* @version $Id$
*/
public interface Permission {
//~ Static fields/initializers =====================================================================================
char RESERVED_ON = '~';
char RESERVED_OFF = '.';
String THIRTY_TWO_RESERVED_OFF = "................................";
//~ Methods ========================================================================================================
/**
* Returns the bits that represents the permission.
*
* @return the bits that represent the permission
*/
int getMask();
/**
* Returns a 32-character long bit pattern <code>String</code> representing this permission.
* <p>
* Implementations are free to format the pattern as they see fit, although under no circumstances may
* {@link #RESERVED_OFF} or {@link #RESERVED_ON} be used within the pattern. An exemption is in the case of
* {@link #RESERVED_OFF} which is used to denote a bit that is off (clear).
* Implementations may also elect to use {@link #RESERVED_ON} internally for computation purposes,
* although this method may not return any <code>String</code> containing {@link #RESERVED_ON}.
* </p>
* <p>The returned String must be 32 characters in length.</p>
* <p>This method is only used for user interface and logging purposes. It is not used in any permission
* calculations. Therefore, duplication of characters within the output is permitted.</p>
*
* @return a 32-character bit pattern
*/
String getPattern();
}
@@ -1,49 +0,0 @@
/* 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.springframework.security.acls;
import org.springframework.security.SpringSecurityException;
/**
* Thrown if an {@link Acl} cannot perform an operation because it only loaded a subset of <code>Sid</code>s and
* the caller has requested details for an unloaded <code>Sid</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class UnloadedSidException extends SpringSecurityException {
//~ Constructors ===================================================================================================
/**
* Constructs an <code>NotFoundException</code> with the specified message.
*
* @param msg the detail message
*/
public UnloadedSidException(String msg) {
super(msg);
}
/**
* Constructs an <code>NotFoundException</code> with the specified message
* and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public UnloadedSidException(String msg, Throwable t) {
super(msg, t);
}
}
@@ -1,133 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AuditableAccessControlEntry;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.sid.Sid;
import org.springframework.util.Assert;
import java.io.Serializable;
/**
* An immutable default implementation of <code>AccessControlEntry</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry {
//~ Instance fields ================================================================================================
private Acl acl;
private Permission permission;
private Serializable id;
private Sid sid;
private boolean auditFailure = false;
private boolean auditSuccess = false;
private boolean granting;
//~ Constructors ===================================================================================================
public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting,
boolean auditSuccess, boolean auditFailure) {
Assert.notNull(acl, "Acl required");
Assert.notNull(sid, "Sid required");
Assert.notNull(permission, "Permission required");
this.id = id;
this.acl = acl; // can be null
this.sid = sid;
this.permission = permission;
this.granting = granting;
this.auditSuccess = auditSuccess;
this.auditFailure = auditFailure;
}
//~ Methods ========================================================================================================
public boolean equals(Object arg0) {
if (!(arg0 instanceof AccessControlEntryImpl)) {
return false;
}
AccessControlEntryImpl rhs = (AccessControlEntryImpl) arg0;
if ((this.auditFailure != rhs.isAuditFailure()) || (this.auditSuccess != rhs.isAuditSuccess())
|| (this.granting != rhs.isGranting()) || !this.acl.equals(rhs.getAcl()) || !this.id.equals(rhs.getId())
|| !this.permission.equals(rhs.getPermission()) || !this.sid.equals(rhs.getSid())) {
return false;
}
return true;
}
public Acl getAcl() {
return acl;
}
public Serializable getId() {
return id;
}
public Permission getPermission() {
return permission;
}
public Sid getSid() {
return sid;
}
public boolean isAuditFailure() {
return auditFailure;
}
public boolean isAuditSuccess() {
return auditSuccess;
}
public boolean isGranting() {
return granting;
}
void setAuditFailure(boolean auditFailure) {
this.auditFailure = auditFailure;
}
void setAuditSuccess(boolean auditSuccess) {
this.auditSuccess = auditSuccess;
}
void setPermission(Permission permission) {
Assert.notNull(permission, "Permission required");
this.permission = permission;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("AccessControlEntryImpl[");
sb.append("id: ").append(this.id).append("; ");
sb.append("granting: ").append(this.granting).append("; ");
sb.append("sid: ").append(this.sid).append("; ");
sb.append("permission: ").append(this.permission).append("; ");
sb.append("auditSuccess: ").append(this.auditSuccess).append("; ");
sb.append("auditFailure: ").append(this.auditFailure);
sb.append("]");
return sb.toString();
}
}
@@ -1,38 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.acls.Acl;
/**
* Strategy used by {@link AclImpl} to determine whether a principal is permitted to call
* adminstrative methods on the <code>AclImpl</code>.
*
* @author Ben Alex
* @version $Id$
*/
public interface AclAuthorizationStrategy {
//~ Static fields/initializers =====================================================================================
int CHANGE_OWNERSHIP = 0;
int CHANGE_AUDITING = 1;
int CHANGE_GENERAL = 2;
//~ Methods ========================================================================================================
void securityCheck(Acl acl, int changeType);
}
@@ -1,126 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.acls.sid.SidRetrievalStrategy;
import org.springframework.security.acls.sid.SidRetrievalStrategyImpl;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* Default implementation of {@link AclAuthorizationStrategy}.<p>Permission will be granted provided the current
* principal is either the owner (as defined by the ACL), has {@link BasePermission#ADMINISTRATION} (as defined by the
* ACL and via a {@link Sid} retrieved for the current principal via {@link #sidRetrievalStrategy}), or if the current
* principal holds the relevant system-wide {@link GrantedAuthority} and injected into the constructor.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
//~ Instance fields ================================================================================================
private GrantedAuthority gaGeneralChanges;
private GrantedAuthority gaModifyAuditing;
private GrantedAuthority gaTakeOwnership;
private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
//~ Constructors ===================================================================================================
/**
* Constructor. The only mandatory parameter relates to the system-wide {@link GrantedAuthority} instances that
* can be held to always permit ACL changes.
*
* @param auths an array of <code>GrantedAuthority</code>s that have
* special permissions (index 0 is the authority needed to change
* ownership, index 1 is the authority needed to modify auditing details,
* index 2 is the authority needed to change other ACL and ACE details) (required)
*/
public AclAuthorizationStrategyImpl(GrantedAuthority[] auths) {
Assert.notEmpty(auths, "GrantedAuthority[] with three elements required");
Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required");
this.gaTakeOwnership = auths[0];
this.gaModifyAuditing = auths[1];
this.gaGeneralChanges = auths[2];
}
//~ Methods ========================================================================================================
public void securityCheck(Acl acl, int changeType) {
if ((SecurityContextHolder.getContext() == null)
|| (SecurityContextHolder.getContext().getAuthentication() == null)
|| !SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
throw new AccessDeniedException("Authenticated principal required to operate with ACLs");
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// Check if authorized by virtue of ACL ownership
Sid currentUser = new PrincipalSid(authentication);
if (currentUser.equals(acl.getOwner())
&& ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) {
return;
}
// Not authorized by ACL ownership; try via adminstrative permissions
GrantedAuthority requiredAuthority = null;
if (changeType == CHANGE_AUDITING) {
requiredAuthority = this.gaModifyAuditing;
} else if (changeType == CHANGE_GENERAL) {
requiredAuthority = this.gaGeneralChanges;
} else if (changeType == CHANGE_OWNERSHIP) {
requiredAuthority = this.gaTakeOwnership;
} else {
throw new IllegalArgumentException("Unknown change type");
}
// Iterate this principal's authorities to determine right
GrantedAuthority[] auths = authentication.getAuthorities();
for (int i = 0; i < auths.length; i++) {
if (requiredAuthority.equals(auths[i])) {
return;
}
}
// Try to get permission via ACEs within the ACL
Sid[] sids = sidRetrievalStrategy.getSids(authentication);
if (acl.isGranted(new Permission[] {BasePermission.ADMINISTRATION}, sids, false)) {
return;
}
throw new AccessDeniedException(
"Principal does not have required ACL permissions to perform requested operation");
}
public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
this.sidRetrievalStrategy = sidRetrievalStrategy;
}
}
@@ -1,407 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AuditableAcl;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.OwnershipAcl;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.UnloadedSidException;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.sid.Sid;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
/**
* Base implementation of <code>Acl</code>.
*
* @author Ben Alex
* @version $Id
*/
public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
//~ Instance fields ================================================================================================
private Acl parentAcl;
private AclAuthorizationStrategy aclAuthorizationStrategy;
private AuditLogger auditLogger;
private List aces = new Vector();
private ObjectIdentity objectIdentity;
private Serializable id;
private Sid owner; // OwnershipAcl
private Sid[] loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
private boolean entriesInheriting = true;
//~ Constructors ===================================================================================================
/**
* Minimal constructor, which should be used {@link
* org.springframework.security.acls.MutableAclService#createAcl(ObjectIdentity)}.
*
* @param objectIdentity the object identity this ACL relates to (required)
* @param id the primary key assigned to this ACL (required)
* @param aclAuthorizationStrategy authorization strategy (required)
* @param auditLogger audit logger (required)
*/
public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
AuditLogger auditLogger) {
Assert.notNull(objectIdentity, "Object Identity required");
Assert.notNull(id, "Id required");
Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
Assert.notNull(auditLogger, "AuditLogger required");
this.objectIdentity = objectIdentity;
this.id = id;
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
this.auditLogger = auditLogger;
}
/**
* Full constructor, which should be used by persistence tools that do not
* provide field-level access features.
*
* @param objectIdentity the object identity this ACL relates to (required)
* @param id the primary key assigned to this ACL (required)
* @param aclAuthorizationStrategy authorization strategy (required)
* @param auditLogger audit logger (required)
* @param parentAcl the parent (may be <code>null</code>)
* @param loadedSids the loaded SIDs if only a subset were loaded (may be
* <code>null</code>)
* @param entriesInheriting if ACEs from the parent should inherit into
* this ACL
* @param owner the owner (required)
*/
public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
AuditLogger auditLogger, Acl parentAcl, Sid[] loadedSids, boolean entriesInheriting, Sid owner) {
Assert.notNull(objectIdentity, "Object Identity required");
Assert.notNull(id, "Id required");
Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
Assert.notNull(owner, "Owner required");
Assert.notNull(auditLogger, "AuditLogger required");
this.objectIdentity = objectIdentity;
this.id = id;
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
this.auditLogger = auditLogger;
this.parentAcl = parentAcl; // may be null
this.loadedSids = loadedSids; // may be null
this.entriesInheriting = entriesInheriting;
this.owner = owner;
}
/**
* Private no-argument constructor for use by reflection-based persistence
* tools along with field-level access.
*/
private AclImpl() {}
//~ Methods ========================================================================================================
public void deleteAce(Serializable aceId) throws NotFoundException {
aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
synchronized (aces) {
int offset = findAceOffset(aceId);
if (offset == -1) {
throw new NotFoundException("Requested ACE ID not found");
}
this.aces.remove(offset);
}
}
private int findAceOffset(Serializable aceId) {
Assert.notNull(aceId, "ACE ID is required");
synchronized (aces) {
for (int i = 0; i < aces.size(); i++) {
AccessControlEntry ace = (AccessControlEntry) aces.get(i);
if (ace.getId().equals(aceId)) {
return i;
}
}
}
return -1;
}
public AccessControlEntry[] getEntries() {
// Can safely return AccessControlEntry directly, as they're immutable outside the ACL package
return (AccessControlEntry[]) aces.toArray(new AccessControlEntry[] {});
}
public Serializable getId() {
return this.id;
}
public ObjectIdentity getObjectIdentity() {
return objectIdentity;
}
public Sid getOwner() {
return this.owner;
}
public Acl getParentAcl() {
return parentAcl;
}
public void insertAce(Serializable afterAceId, Permission permission, Sid sid, boolean granting)
throws NotFoundException {
aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.notNull(permission, "Permission required");
Assert.notNull(sid, "Sid required");
AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this, sid, permission, granting, false, false);
synchronized (aces) {
if (afterAceId != null) {
int offset = findAceOffset(afterAceId);
if (offset == -1) {
throw new NotFoundException("Requested ACE ID not found");
}
this.aces.add(offset + 1, ace);
} else {
this.aces.add(ace);
}
}
}
public boolean isEntriesInheriting() {
return entriesInheriting;
}
/**
* Determines authorization. The order of the <code>permission</code> and <code>sid</code> arguments is
* <em>extremely important</em>! The method will iterate through each of the <code>permission</code>s in the order
* specified. For each iteration, all of the <code>sid</code>s will be considered, again in the order they are
* presented. A search will then be performed for the first {@link AccessControlEntry} object that directly
* matches that <code>permission:sid</code> combination. When the <em>first full match</em> is found (ie an ACE
* that has the SID currently being searched for and the exact permission bit mask being search for), the grant or
* deny flag for that ACE will prevail. If the ACE specifies to grant access, the method will return
* <code>true</code>. If the ACE specifies to deny access, the loop will stop and the next <code>permission</code>
* iteration will be performed. If each permission indicates to deny access, the first deny ACE found will be
* considered the reason for the failure (as it was the first match found, and is therefore the one most logically
* requiring changes - although not always). If absolutely no matching ACE was found at all for any permission,
* the parent ACL will be tried (provided that there is a parent and {@link #isEntriesInheriting()} is
* <code>true</code>. The parent ACL will also scan its parent and so on. If ultimately no matching ACE is found,
* a <code>NotFoundException</code> will be thrown and the caller will need to decide how to handle the permission
* check. Similarly, if any of the SID arguments presented to the method were not loaded by the ACL,
* <code>UnloadedSidException</code> will be thrown.
*
* @param permission the exact permissions to scan for (order is important)
* @param sids the exact SIDs to scan for (order is important)
* @param administrativeMode if <code>true</code> denotes the query is for administrative purposes and no auditing
* will be undertaken
*
* @return <code>true</code> if one of the permissions has been granted, <code>false</code> if one of the
* permissions has been specifically revoked
*
* @throws NotFoundException if an exact ACE for one of the permission bit masks and SID combination could not be
* found
* @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the ACL was only loaded for a
* subset of SIDs
*/
public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
throws NotFoundException, UnloadedSidException {
Assert.notEmpty(permission, "Permissions required");
Assert.notEmpty(sids, "SIDs required");
if (!this.isSidLoaded(sids)) {
throw new UnloadedSidException("ACL was not loaded for one or more SID");
}
AccessControlEntry firstRejection = null;
for (int i = 0; i < permission.length; i++) {
for (int x = 0; x < sids.length; x++) {
// Attempt to find exact match for this permission mask and SID
Iterator acesIterator = aces.iterator();
boolean scanNextSid = true;
while (acesIterator.hasNext()) {
AccessControlEntry ace = (AccessControlEntry) acesIterator.next();
if ((ace.getPermission().getMask() == permission[i].getMask()) && ace.getSid().equals(sids[x])) {
// Found a matching ACE, so its authorization decision will prevail
if (ace.isGranting()) {
// Success
if (!administrativeMode) {
auditLogger.logIfNeeded(true, ace);
}
return true;
} else {
// Failure for this permission, so stop search
// We will see if they have a different permission
// (this permission is 100% rejected for this SID)
if (firstRejection == null) {
// Store first rejection for auditing reasons
firstRejection = ace;
}
scanNextSid = false; // helps break the loop
break; // exit "aceIterator" while loop
}
}
}
if (!scanNextSid) {
break; // exit SID for loop (now try next permission)
}
}
}
if (firstRejection != null) {
// We found an ACE to reject the request at this point, as no
// other ACEs were found that granted a different permission
if (!administrativeMode) {
auditLogger.logIfNeeded(false, firstRejection);
}
return false;
}
// No matches have been found so far
if (isEntriesInheriting() && (parentAcl != null)) {
// We have a parent, so let them try to find a matching ACE
return parentAcl.isGranted(permission, sids, false);
} else {
// We either have no parent, or we're the uppermost parent
throw new NotFoundException("Unable to locate a matching ACE for passed permissions and SIDs");
}
}
public boolean isSidLoaded(Sid[] 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.length == 0)) {
return true;
}
// This ACL applies to a SID subset only. Iterate to check it applies.
for (int i = 0; i < sids.length; i++) {
boolean found = false;
for (int y = 0; y < this.loadedSids.length; y++) {
if (sids[i].equals(this.loadedSids[y])) {
// this SID is OK
found = true;
break; // out of loadedSids for loop
}
}
if (!found) {
return false;
}
}
return true;
}
public void setEntriesInheriting(boolean entriesInheriting) {
aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
this.entriesInheriting = entriesInheriting;
}
public void setOwner(Sid newOwner) {
aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.notNull(newOwner, "Owner required");
this.owner = newOwner;
}
public void setParent(Acl newParent) {
aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.notNull(newParent, "New Parent required");
Assert.isTrue(!newParent.equals(this), "Cannot be the parent of yourself");
this.parentAcl = newParent;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("AclImpl[");
sb.append("id: ").append(this.id).append("; ");
sb.append("objectIdentity: ").append(this.objectIdentity).append("; ");
sb.append("owner: ").append(this.owner).append("; ");
Iterator iterator = this.aces.iterator();
int count = 0;
while (iterator.hasNext()) {
count++;
if (count == 1) {
sb.append("\r\n");
}
sb.append(iterator.next().toString()).append("\r\n");
}
if (count == 0) {
sb.append("no ACEs; ");
}
sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString());
sb.append("]");
return sb.toString();
}
public void updateAce(Serializable aceId, Permission permission)
throws NotFoundException {
aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
synchronized (aces) {
int offset = findAceOffset(aceId);
if (offset == -1) {
throw new NotFoundException("Requested ACE ID not found");
}
AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
ace.setPermission(permission);
}
}
public void updateAuditing(Serializable aceId, boolean auditSuccess, boolean auditFailure) {
aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_AUDITING);
synchronized (aces) {
int offset = findAceOffset(aceId);
if (offset == -1) {
throw new NotFoundException("Requested ACE ID not found");
}
AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
ace.setAuditSuccess(auditSuccess);
ace.setAuditFailure(auditFailure);
}
}
}
@@ -1,31 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.acls.AccessControlEntry;
/**
* Used by <code>AclImpl</code> to log audit events.
*
* @author Ben Alex
* @version $Id$
*
*/
public interface AuditLogger {
//~ Methods ========================================================================================================
void logIfNeeded(boolean granted, AccessControlEntry ace);
}
@@ -1,170 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.acls.AclFormattingUtils;
import org.springframework.security.acls.Permission;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* A set of standard permissions.
*
* @author Ben Alex
* @version $Id$
*/
public final class BasePermission implements Permission {
//~ Static fields/initializers =====================================================================================
public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16
private static Map locallyDeclaredPermissionsByInteger = new HashMap();
private static Map locallyDeclaredPermissionsByName = new HashMap();
static {
Field[] fields = BasePermission.class.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
try {
Object fieldValue = fields[i].get(null);
if (BasePermission.class.isAssignableFrom(fieldValue.getClass())) {
// Found a BasePermission static field
BasePermission perm = (BasePermission) fieldValue;
locallyDeclaredPermissionsByInteger.put(new Integer(perm.getMask()), perm);
locallyDeclaredPermissionsByName.put(fields[i].getName(), perm);
}
} catch (Exception ignore) {}
}
}
//~ Instance fields ================================================================================================
private char code;
private int mask;
//~ Constructors ===================================================================================================
private BasePermission(int mask, char code) {
this.mask = mask;
this.code = code;
}
//~ Methods ========================================================================================================
/**
* Dynamically creates a <code>CumulativePermission</code> or <code>BasePermission</code> representing the
* active bits in the passed mask.
*
* @param mask to build
*
* @return a Permission representing the requested object
*/
public static Permission buildFromMask(int mask) {
if (locallyDeclaredPermissionsByInteger.containsKey(new Integer(mask))) {
// The requested mask has an exactly match against a statically-defined BasePermission, so return it
return (Permission) locallyDeclaredPermissionsByInteger.get(new Integer(mask));
}
// To get this far, we have to use a CumulativePermission
CumulativePermission permission = new CumulativePermission();
for (int i = 0; i < 32; i++) {
int permissionToCheck = 1 << i;
if ((mask & permissionToCheck) == permissionToCheck) {
Permission p = (Permission) locallyDeclaredPermissionsByInteger.get(new Integer(permissionToCheck));
Assert.state(p != null,
"Mask " + permissionToCheck + " does not have a corresponding static BasePermission");
permission.set(p);
}
}
return permission;
}
public static Permission[] buildFromMask(int[] masks) {
if ((masks == null) || (masks.length == 0)) {
return new Permission[0];
}
Permission[] permissions = new Permission[masks.length];
for (int i = 0; i < masks.length; i++) {
permissions[i] = buildFromMask(masks[i]);
}
return permissions;
}
public static Permission buildFromName(String name) {
Assert.isTrue(locallyDeclaredPermissionsByName.containsKey(name), "Unknown permission '" + name + "'");
return (Permission) locallyDeclaredPermissionsByName.get(name);
}
public static Permission[] buildFromName(String[] names) {
if ((names == null) || (names.length == 0)) {
return new Permission[0];
}
Permission[] permissions = new Permission[names.length];
for (int i = 0; i < names.length; i++) {
permissions[i] = buildFromName(names[i]);
}
return permissions;
}
public boolean equals(Object arg0) {
if (arg0 == null) {
return false;
}
if (!(arg0 instanceof Permission)) {
return false;
}
Permission rhs = (Permission) arg0;
return (this.mask == rhs.getMask());
}
public int getMask() {
return mask;
}
public String getPattern() {
return AclFormattingUtils.printBinary(mask, code);
}
public String toString() {
return "BasePermission[" + getPattern() + "=" + mask + "]";
}
public int hashCode() {
return this.mask;
}
}
@@ -1,45 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.AuditableAccessControlEntry;
import org.springframework.util.Assert;
/**
* A bsaic implementation of {@link AuditLogger}.
*
* @author Ben Alex
* @version $Id$
*/
public class ConsoleAuditLogger implements AuditLogger {
//~ Methods ========================================================================================================
public void logIfNeeded(boolean granted, AccessControlEntry ace) {
Assert.notNull(ace, "AccessControlEntry required");
if (ace instanceof AuditableAccessControlEntry) {
AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace;
if (granted && auditableAce.isAuditSuccess()) {
System.out.println("GRANTED due to ACE: " + ace);
} else if (!granted && auditableAce.isAuditFailure()) {
System.out.println("DENIED due to ACE: " + ace);
}
}
}
}
@@ -1,88 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.springframework.security.acls.AclFormattingUtils;
import org.springframework.security.acls.Permission;
/**
* Represents a <code>Permission</code> that is constructed at runtime from other permissions.<p>Methods return
* <code>this</code>, in order to facilitate method chaining.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class CumulativePermission implements Permission {
//~ Instance fields ================================================================================================
private String pattern = THIRTY_TWO_RESERVED_OFF;
private int mask = 0;
//~ Methods ========================================================================================================
public CumulativePermission clear(Permission permission) {
this.mask &= ~permission.getMask();
this.pattern = AclFormattingUtils.demergePatterns(this.pattern, permission.getPattern());
return this;
}
public CumulativePermission clear() {
this.mask = 0;
this.pattern = THIRTY_TWO_RESERVED_OFF;
return this;
}
public boolean equals(Object arg0) {
if (arg0 == null)
{
return false;
}
if (!(arg0 instanceof Permission)) {
return false;
}
Permission rhs = (Permission) arg0;
return (this.mask == rhs.getMask());
}
public int hashCode() {
return this.mask;
}
public int getMask() {
return this.mask;
}
public String getPattern() {
return this.pattern;
}
public CumulativePermission set(Permission permission) {
this.mask |= permission.getMask();
this.pattern = AclFormattingUtils.mergePatterns(this.pattern, permission.getPattern());
return this;
}
public String toString() {
return "CumulativePermission[" + pattern + "=" + this.mask + "]";
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Basic implementation of access control lists (ACLs) interfaces.
</body>
</html>
@@ -1,42 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import java.io.Serializable;
/**
* A caching layer for {@link JdbcAclService}.
*
* @author Ben Alex
* @version $Id$
*
*/
public interface AclCache {
//~ Methods ========================================================================================================
void evictFromCache(Serializable pk);
void evictFromCache(ObjectIdentity objectIdentity);
MutableAcl getFromCache(ObjectIdentity objectIdentity);
MutableAcl getFromCache(Serializable pk);
void putInCache(MutableAcl acl);
}
@@ -1,525 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.UnloadedSidException;
import org.springframework.security.acls.domain.AccessControlEntryImpl;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.AuditLogger;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.acls.sid.GrantedAuthoritySid;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.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.<p>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 <code>LookupStrategy</code>. This class does not support
* subclassing, as it is likely to change in future releases and therefore subclassing is unsupported.</p>
*
* @author Ben Alex
* @version $Id$
*/
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 <code>Map</code> of <code>AclImpl</code> instances which contain
* <code>StubAclParent</code>s into proper, valid <code>AclImpl</code>s with correct ACL parents.
*
* @param inputMap the unconverted <code>AclImpl</code>s
* @param currentIdentity the current<code>Acl</code> 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 <code>ResultSet</code> row, and converts it into an <code>AclImpl</code> that
* contains a <code>StubAclParent</code>
*
* @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 <code>ObjectIdentity</code>s directly from the database.<p>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.</p>
* <p>This subclass is required to return fully valid <code>Acl</code>s, including properly-configured
* parent ACLs.</p>
*
* @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.<p>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.</p>
* <p>The implementation works in batch sizes specfied by {@link #batchSize}.</p>
*
* @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<ObjectIdentity,Acl>
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");
}
}
}
@@ -1,115 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import net.sf.ehcache.Ehcache;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.util.Assert;
import java.io.Serializable;
/**
* Simple implementation of {@link AclCache} that delegates to EH-CACHE.
*
* @author Ben Alex
* @version $Id$
*/
public class EhCacheBasedAclCache implements AclCache {
//~ Instance fields ================================================================================================
private Ehcache cache;
//~ Constructors ===================================================================================================
public EhCacheBasedAclCache(Ehcache cache) {
Assert.notNull(cache, "Cache required");
this.cache = cache;
}
//~ Methods ========================================================================================================
public void evictFromCache(Serializable pk) {
Assert.notNull(pk, "Primary key (identifier) required");
MutableAcl acl = getFromCache(pk);
if (acl != null) {
cache.remove(acl.getId());
cache.remove(acl.getObjectIdentity());
}
}
public void evictFromCache(ObjectIdentity objectIdentity) {
Assert.notNull(objectIdentity, "ObjectIdentity required");
MutableAcl acl = getFromCache(objectIdentity);
if (acl != null) {
cache.remove(acl.getId());
cache.remove(acl.getObjectIdentity());
}
}
public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
Assert.notNull(objectIdentity, "ObjectIdentity required");
Element element = null;
try {
element = cache.get(objectIdentity);
} catch (CacheException ignored) {}
if (element == null) {
return null;
}
return (MutableAcl) element.getValue();
}
public MutableAcl getFromCache(Serializable pk) {
Assert.notNull(pk, "Primary key (identifier) required");
Element element = null;
try {
element = cache.get(pk);
} catch (CacheException ignored) {}
if (element == null) {
return null;
}
return (MutableAcl) element.getValue();
}
public void putInCache(MutableAcl acl) {
Assert.notNull(acl, "Acl required");
Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
Assert.notNull(acl.getId(), "ID required");
if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
putInCache((MutableAcl) acl.getParentAcl());
}
cache.put(new Element(acl.getObjectIdentity(), acl));
cache.put(new Element(acl.getId(), acl));
}
}
@@ -1,114 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AclService;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.acls.sid.Sid;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.Assert;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
/**
* Simple JDBC-based implementation of <code>AclService</code>.
* <p>
* Requires the "dirty" flags in {@link org.springframework.security.acls.domain.AclImpl} and
* {@link org.springframework.security.acls.domain.AccessControlEntryImpl} to be set, so that the implementation can
* detect changed parameters easily.
*
* @author Ben Alex
* @version $Id$
*/
public class JdbcAclService implements AclService {
//~ Static fields/initializers =====================================================================================
protected static final Log log = LogFactory.getLog(JdbcAclService.class);
private static final String selectAclObjectWithParent = "SELECT obj.object_id_identity obj_id, class.class class "
+ "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 = ?)";
//~ Instance fields ================================================================================================
protected JdbcTemplate jdbcTemplate;
private LookupStrategy lookupStrategy;
//~ Constructors ===================================================================================================
public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) {
Assert.notNull(dataSource, "DataSource required");
Assert.notNull(lookupStrategy, "LookupStrategy required");
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.lookupStrategy = lookupStrategy;
}
//~ Methods ========================================================================================================
public ObjectIdentity[] findChildren(ObjectIdentity parentIdentity) {
Object[] args = {parentIdentity.getIdentifier(), parentIdentity.getJavaType().getName()};
List objects = jdbcTemplate.query(selectAclObjectWithParent, args,
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum)
throws SQLException {
String javaType = rs.getString("class");
String identifier = rs.getString("obj_id");
return new ObjectIdentityImpl(javaType, identifier);
}
});
return (ObjectIdentityImpl[]) objects.toArray(new ObjectIdentityImpl[] {});
}
public Acl readAclById(ObjectIdentity object, Sid[] sids) throws NotFoundException {
Map map = readAclsById(new ObjectIdentity[] {object}, sids);
if (map.size() == 0) {
throw new NotFoundException("Could not find ACL");
}
return (Acl) map.get(object);
}
public Acl readAclById(ObjectIdentity object) throws NotFoundException {
return readAclById(object, null);
}
public Map readAclsById(ObjectIdentity[] objects) {
return readAclsById(objects, null);
}
public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException {
return lookupStrategy.readAclsById(objects, sids);
}
}
@@ -1,371 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import org.springframework.security.Authentication;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AlreadyExistsException;
import org.springframework.security.acls.ChildrenExistException;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.MutableAclService;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.domain.AccessControlEntryImpl;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.acls.sid.GrantedAuthoritySid;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import java.lang.reflect.Array;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
/**
* Provides a base implementation of {@link MutableAclService}.
*
* @author Ben Alex
* @author Johannes Zlattinger
* @version $Id$
*/
public class JdbcMutableAclService extends JdbcAclService implements MutableAclService {
//~ Instance fields ================================================================================================
private AclCache aclCache;
private String deleteClassByClassNameString = "DELETE FROM acl_class WHERE class=?";
private String deleteEntryByObjectIdentityForeignKey = "DELETE FROM acl_entry WHERE acl_object_identity=?";
private String deleteObjectIdentityByPrimaryKey = "DELETE FROM acl_object_identity WHERE id=?";
private String identityQuery = "call identity()";
private String insertClass = "INSERT INTO acl_class (id, class) VALUES (null, ?)";
private String insertEntry = "INSERT INTO acl_entry "
+ "(id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)"
+ "VALUES (null, ?, ?, ?, ?, ?, ?, ?)";
private String insertObjectIdentity = "INSERT INTO acl_object_identity "
+ "(id, object_id_class, object_id_identity, owner_sid, entries_inheriting) " + "VALUES (null, ?, ?, ?, ?)";
private String insertSid = "INSERT INTO acl_sid (id, principal, sid) VALUES (null, ?, ?)";
private String selectClassPrimaryKey = "SELECT id FROM acl_class WHERE class=?";
private String selectCountObjectIdentityRowsForParticularClassNameString = "SELECT COUNT(acl_object_identity.id) "
+ "FROM acl_object_identity, acl_class WHERE acl_class.id = acl_object_identity.object_id_class and class=?";
private String selectObjectIdentityPrimaryKey = "SELECT acl_object_identity.id FROM acl_object_identity, acl_class "
+ "WHERE acl_object_identity.object_id_class = acl_class.id and acl_class.class=? "
+ "and acl_object_identity.object_id_identity = ?";
private String selectSidPrimaryKey = "SELECT id FROM acl_sid WHERE principal=? AND sid=?";
private String updateObjectIdentity = "UPDATE acl_object_identity SET "
+ "parent_object = ?, owner_sid = ?, entries_inheriting = ?" + " where id = ?";
//~ Constructors ===================================================================================================
public JdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
super(dataSource, lookupStrategy);
Assert.notNull(aclCache, "AclCache required");
this.aclCache = aclCache;
}
//~ Methods ========================================================================================================
public MutableAcl createAcl(ObjectIdentity objectIdentity)
throws AlreadyExistsException {
Assert.notNull(objectIdentity, "Object Identity required");
// Check this object identity hasn't already been persisted
if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) {
throw new AlreadyExistsException("Object identity '" + objectIdentity + "' already exists");
}
// Need to retrieve the current principal, in order to know who "owns" this ACL (can be changed later on)
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
PrincipalSid sid = new PrincipalSid(auth);
// Create the acl_object_identity row
createObjectIdentity(objectIdentity, sid);
// Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
Acl acl = readAclById(objectIdentity);
Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned");
return (MutableAcl) acl;
}
/**
* Creates a new row in acl_entry for every ACE defined in the passed MutableAcl object.
*
* @param acl containing the ACEs to insert
*/
protected void createEntries(final MutableAcl acl) {
jdbcTemplate.batchUpdate(insertEntry,
new BatchPreparedStatementSetter() {
public int getBatchSize() {
return acl.getEntries().length;
}
public void setValues(PreparedStatement stmt, int i)
throws SQLException {
AccessControlEntry entry_ = (AccessControlEntry) Array.get(acl.getEntries(), i);
Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class");
AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_;
stmt.setLong(1, ((Long) acl.getId()).longValue());
stmt.setInt(2, i);
stmt.setLong(3, createOrRetrieveSidPrimaryKey(entry.getSid(), true).longValue());
stmt.setInt(4, entry.getPermission().getMask());
stmt.setBoolean(5, entry.isGranting());
stmt.setBoolean(6, entry.isAuditSuccess());
stmt.setBoolean(7, entry.isAuditFailure());
}
});
}
/**
* Creates an entry in the acl_object_identity table for the passed ObjectIdentity. The Sid is also
* necessary, as acl_object_identity has defined the sid column as non-null.
*
* @param object to represent an acl_object_identity for
* @param owner for the SID column (will be created if there is no acl_sid entry for this particular Sid already)
*/
protected void createObjectIdentity(ObjectIdentity object, Sid owner) {
Long sidId = createOrRetrieveSidPrimaryKey(owner, true);
Long classId = createOrRetrieveClassPrimaryKey(object.getJavaType(), true);
jdbcTemplate.update(insertObjectIdentity,
new Object[] {classId, object.getIdentifier().toString(), sidId, new Boolean(true)});
}
/**
* Retrieves the primary key from acl_class, creating a new row if needed and the allowCreate property is
* true.
*
* @param clazz to find or create an entry for (this implementation uses the fully-qualified class name String)
* @param allowCreate true if creation is permitted if not found
*
* @return the primary key or null if not found
*/
protected Long createOrRetrieveClassPrimaryKey(Class clazz, boolean allowCreate) {
List classIds = jdbcTemplate.queryForList(selectClassPrimaryKey, new Object[] {clazz.getName()}, Long.class);
Long classId = null;
if (classIds.isEmpty()) {
if (allowCreate) {
classId = null;
jdbcTemplate.update(insertClass, new Object[] {clazz.getName()});
Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(),
"Transaction must be running");
classId = new Long(jdbcTemplate.queryForLong(identityQuery));
}
} else {
classId = (Long) classIds.iterator().next();
}
return classId;
}
/**
* Retrieves the primary key from acl_sid, creating a new row if needed and the allowCreate property is
* true.
*
* @param sid to find or create
* @param allowCreate true if creation is permitted if not found
*
* @return the primary key or null if not found
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
Assert.notNull(sid, "Sid required");
String sidName = null;
boolean principal = true;
if (sid instanceof PrincipalSid) {
sidName = ((PrincipalSid) sid).getPrincipal();
} else if (sid instanceof GrantedAuthoritySid) {
sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority();
principal = false;
} else {
throw new IllegalArgumentException("Unsupported implementation of Sid");
}
List sidIds = jdbcTemplate.queryForList(selectSidPrimaryKey, new Object[] {new Boolean(principal), sidName},
Long.class);
Long sidId = null;
if (sidIds.isEmpty()) {
if (allowCreate) {
sidId = null;
jdbcTemplate.update(insertSid, new Object[] {new Boolean(principal), sidName});
Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(),
"Transaction must be running");
sidId = new Long(jdbcTemplate.queryForLong(identityQuery));
}
} else {
sidId = (Long) sidIds.iterator().next();
}
return sidId;
}
public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
throws ChildrenExistException {
Assert.notNull(objectIdentity, "Object Identity required");
Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier");
// Recursively call this method for children, or handle children if they don't want automatic recursion
ObjectIdentity[] children = findChildren(objectIdentity);
if (deleteChildren) {
for (int i = 0; i < children.length; i++) {
deleteAcl(children[i], true);
}
} else if (children.length > 0) {
throw new ChildrenExistException("Cannot delete '" + objectIdentity + "' (has " + children.length
+ " children)");
}
// Delete this ACL's ACEs in the acl_entry table
deleteEntries(objectIdentity);
// Delete this ACL's acl_object_identity row
deleteObjectIdentityAndOptionallyClass(objectIdentity);
// Clear the cache
aclCache.evictFromCache(objectIdentity);
}
/**
* Deletes all ACEs defined in the acl_entry table belonging to the presented ObjectIdentity
*
* @param oid the rows in acl_entry to delete
*/
protected void deleteEntries(ObjectIdentity oid) {
jdbcTemplate.update(deleteEntryByObjectIdentityForeignKey,
new Object[] {retrieveObjectIdentityPrimaryKey(oid)});
}
/**
* Deletes a single row from acl_object_identity that is associated with the presented ObjectIdentity. In
* addition, deletes the corresponding row from acl_class if there are no more entries in acl_object_identity that
* use that particular acl_class. This keeps the acl_class table reasonably small.
*
* @param oid to delete the acl_object_identity (and clean up acl_class for that class name if appropriate)
*/
protected void deleteObjectIdentityAndOptionallyClass(ObjectIdentity oid) {
// Delete the acl_object_identity row
jdbcTemplate.update(deleteObjectIdentityByPrimaryKey, new Object[] {retrieveObjectIdentityPrimaryKey(oid)});
// Delete the acl_class row, assuming there are no other references to it in acl_object_identity
Object[] className = {oid.getJavaType().getName()};
long numObjectIdentities = jdbcTemplate.queryForLong(selectCountObjectIdentityRowsForParticularClassNameString,
className);
if (numObjectIdentities == 0) {
// No more rows
jdbcTemplate.update(deleteClassByClassNameString, className);
}
}
/**
* Retrieves the primary key from the acl_object_identity table for the passed ObjectIdentity. Unlike some
* other methods in this implementation, this method will NOT create a row (use {@link
* #createObjectIdentity(ObjectIdentity, Sid)} instead).
*
* @param oid to find
*
* @return the object identity or null if not found
*/
protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) {
try {
return new Long(jdbcTemplate.queryForLong(selectObjectIdentityPrimaryKey,
new Object[] {oid.getJavaType().getName(), oid.getIdentifier()}));
} catch (DataAccessException notFound) {
return null;
}
}
/**
* This implementation will simply delete all ACEs in the database and recreate them on each invocation of
* this method. A more comprehensive implementation might use dirty state checking, or more likely use ORM
* capabilities for create, update and delete operations of {@link MutableAcl}.
*
* @param acl DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws NotFoundException DOCUMENT ME!
*/
public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier");
// Delete this ACL's ACEs in the acl_entry table
deleteEntries(acl.getObjectIdentity());
// Create this ACL's ACEs in the acl_entry table
createEntries(acl);
// Change the mutable columns in acl_object_identity
updateObjectIdentity(acl);
// Clear the cache
aclCache.evictFromCache(acl.getObjectIdentity());
// Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
return (MutableAcl) super.readAclById(acl.getObjectIdentity());
}
/**
* Updates an existing acl_object_identity row, with new information presented in the passed MutableAcl
* object. Also will create an acl_sid entry if needed for the Sid that owns the MutableAcl.
*
* @param acl to modify (a row must already exist in acl_object_identity)
*
* @throws NotFoundException DOCUMENT ME!
*/
protected void updateObjectIdentity(MutableAcl acl) {
Long parentId = null;
if (acl.getParentAcl() != null) {
Assert.isInstanceOf(ObjectIdentityImpl.class, acl.getParentAcl().getObjectIdentity(),
"Implementation only supports ObjectIdentityImpl");
ObjectIdentityImpl oii = (ObjectIdentityImpl) acl.getParentAcl().getObjectIdentity();
parentId = retrieveObjectIdentityPrimaryKey(oii);
}
Assert.notNull(acl.getOwner(), "Owner is required in this implementation");
Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(), true);
int count = jdbcTemplate.update(updateObjectIdentity,
new Object[] {parentId, ownerSid, new Boolean(acl.isEntriesInheriting()), acl.getId()});
if (count != 1) {
throw new NotFoundException("Unable to locate ACL to update");
}
}
}
@@ -1,43 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.sid.Sid;
import java.util.Map;
/**
* Performs lookups for {@link org.springframework.security.acls.AclService}.
*
* @author Ben Alex
* @version $Id$
*/
public interface LookupStrategy {
//~ Methods ========================================================================================================
/**
* Perform database-specific optimized lookup.
*
* @param objects the identities to lookup (required)
* @param sids the SIDs for which identities are required (may be <code>null</code> - implementations may elect not
* to provide SID optimisations)
*
* @return the <code>Map</code> pursuant to the interface contract for {@link
* org.springframework.security.acls.AclService#readAclsById(ObjectIdentity[], Sid[])}
*/
Map readAclsById(ObjectIdentity[] objects, Sid[] sids);
}
@@ -1,5 +0,0 @@
<html>
<body>
JDBC-based persistence of ACL information.
</body>
</html>
@@ -1,70 +0,0 @@
/* 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.springframework.security.acls.objectidentity;
import java.io.Serializable;
/**
* Interface representing the identity of an individual domain object instance.
*
* <P>
* As implementations are used as the key for caching and lookup, it is
* essential that implementations provide methods so that object-equality
* rather than reference-equality can be relied upon by caches. In other
* words, a cache can consider two <code>ObjectIdentity</code>s equal if
* <code>identity1.equals(identity2)</code>, rather than reference-equality of
* <code>identity1==identity2</code>.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface ObjectIdentity extends Serializable {
//~ Methods ========================================================================================================
/**
* Refer to the <code>java.lang.Object</code> documentation for the interface contract.
*
* @param obj to be compared
*
* @return <code>true</code> if the objects are equal, <code>false</code> otherwise
*/
boolean equals(Object obj);
/**
* Obtains the actual identifier. This identifier must not be reused to represent other domain objects with
* the same <code>javaType</code>.<p>Because ACLs are largely immutable, it is strongly recommended to use
* a synthetic identifier (such as a database sequence number for the primary key). Do not use an identifier with
* business meaning, as that business meaning may change.</p>
*
* @return the identifier (unique within this <code>javaType</code>
*/
Serializable getIdentifier();
/**
* Obtains the Java type represented by the domain object.
*
* @return the Java type of the domain object
*/
Class getJavaType();
/**
* Refer to the <code>java.lang.Object</code> documentation for the interface contract.
*
* @return a hash code representation of this object
*/
int hashCode();
}
@@ -1,151 +0,0 @@
/* 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.springframework.security.acls.objectidentity;
import org.springframework.security.acls.IdentityUnavailableException;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import java.io.Serializable;
import java.lang.reflect.Method;
/**
* Simple implementation of {@link ObjectIdentity}.
* <p>
* Uses <code>String</code>s to store the identity of the domain object instance. Also offers a constructor that uses
* reflection to build the identity information.
*
* @author Ben Alex
* @version $Id$
*/
public class ObjectIdentityImpl implements ObjectIdentity {
//~ Instance fields ================================================================================================
private Class javaType;
private Serializable identifier;
//~ Constructors ===================================================================================================
public ObjectIdentityImpl(String javaType, Serializable identifier) {
Assert.hasText(javaType, "Java Type required");
Assert.notNull(identifier, "identifier required");
try {
this.javaType = Class.forName(javaType);
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
this.identifier = identifier;
}
public ObjectIdentityImpl(Class javaType, Serializable identifier) {
Assert.notNull(javaType, "Java Type required");
Assert.notNull(identifier, "identifier required");
this.javaType = javaType;
this.identifier = identifier;
}
/**
* Creates the <code>ObjectIdentityImpl</code> based on the passed
* object instance. The passed object must provide a <code>getId()</code>
* method, otherwise an exception will be thrown. The object passed will
* be considered the {@link #javaType}, so if more control is required,
* an alternate constructor should be used instead.
*
* @param object the domain object instance to create an identity for
*
* @throws IdentityUnavailableException if identity could not be extracted
*/
public ObjectIdentityImpl(Object object) throws IdentityUnavailableException {
Assert.notNull(object, "object cannot be null");
this.javaType = object.getClass();
Object result;
try {
Method method = this.javaType.getMethod("getId", new Class[] {});
result = method.invoke(object, new Object[] {});
} catch (Exception e) {
throw new IdentityUnavailableException("Could not extract identity from object " + object, e);
}
Assert.notNull(result, "getId() is required to return a non-null value");
Assert.isInstanceOf(Serializable.class, result, "Getter must provide a return value of type Serializable");
this.identifier = (Serializable) result;
}
//~ Methods ========================================================================================================
/**
* Important so caching operates properly.<P>Considers an object of the same class equal if it has the same
* <code>classname</code> and <code>id</code> properties.</p>
*
* @param arg0 object to compare
*
* @return <code>true</code> if the presented object matches this object
*/
public boolean equals(Object arg0) {
if (arg0 == null) {
return false;
}
if (!(arg0 instanceof ObjectIdentityImpl)) {
return false;
}
ObjectIdentityImpl other = (ObjectIdentityImpl) arg0;
if (this.getIdentifier().equals(other.getIdentifier()) && this.getJavaType().equals(other.getJavaType())) {
return true;
}
return false;
}
public Serializable getIdentifier() {
return identifier;
}
public Class getJavaType() {
return javaType;
}
/**
* Important so caching operates properly.
*
* @return the hash
*/
public int hashCode() {
int code = 31;
code ^= this.javaType.hashCode();
code ^= this.identifier.hashCode();
return code;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(this.getClass().getName()).append("[");
sb.append("Java Type: ").append(this.javaType);
sb.append("; Identifier: ").append(this.identifier).append("]");
return sb.toString();
}
}
@@ -1,30 +0,0 @@
/* 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.springframework.security.acls.objectidentity;
/**
* Strategy interface that provides the ability to determine which {@link ObjectIdentity}
* will be returned for a particular domain object
*
* @author Ben Alex
* @version $Id$
*
*/
public interface ObjectIdentityRetrievalStrategy {
//~ Methods ========================================================================================================
ObjectIdentity getObjectIdentity(Object domainObject);
}
@@ -1,31 +0,0 @@
/* 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.springframework.security.acls.objectidentity;
/**
* Basic implementation of {@link ObjectIdentityRetrievalStrategy} that uses the constructor of {@link
* ObjectIdentityImpl} to create the {@link ObjectIdentity}.
*
* @author Ben Alex
* @version $Id$
*/
public class ObjectIdentityRetrievalStrategyImpl implements ObjectIdentityRetrievalStrategy {
//~ Methods ========================================================================================================
public ObjectIdentity getObjectIdentity(Object domainObject) {
return new ObjectIdentityImpl(domainObject);
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Provides indirection between ACL packages and domain objects.
</body>
</html>
@@ -1,5 +0,0 @@
<html>
<body>
Interfaces and shared classes to manage access control lists (ACLs) for domain object instances.
</body>
</html>
@@ -1,71 +0,0 @@
/* 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.springframework.security.acls.sid;
import org.springframework.security.GrantedAuthority;
import org.springframework.util.Assert;
/**
* Represents a <code>GrantedAuthority</code> as a <code>Sid</code>.<p>This is a basic implementation that simply
* uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal objects may
* wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class GrantedAuthoritySid implements Sid {
//~ Instance fields ================================================================================================
private String grantedAuthority;
//~ Constructors ===================================================================================================
public GrantedAuthoritySid(String grantedAuthority) {
Assert.hasText(grantedAuthority, "GrantedAuthority required");
this.grantedAuthority = grantedAuthority;
}
public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
Assert.notNull(grantedAuthority, "GrantedAuthority required");
Assert.notNull(grantedAuthority.getAuthority(),
"This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
this.grantedAuthority = grantedAuthority.getAuthority();
}
//~ Methods ========================================================================================================
public boolean equals(Object object) {
if ((object == null) || !(object instanceof GrantedAuthoritySid)) {
return false;
}
// Delegate to getGrantedAuthority() to perform actual comparison (both should be identical)
return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(this.getGrantedAuthority());
}
public int hashCode() {
return this.getGrantedAuthority().hashCode();
}
public String getGrantedAuthority() {
return grantedAuthority;
}
public String toString() {
return "GrantedAuthoritySid[" + this.grantedAuthority + "]";
}
}
@@ -1,77 +0,0 @@
/* 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.springframework.security.acls.sid;
import org.springframework.security.Authentication;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.util.Assert;
/**
* Represents an <code>Authentication.getPrincipal()</code> as a <code>Sid</code>.<p>This is a basic implementation
* that simply uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal
* objects may wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class PrincipalSid implements Sid {
//~ Instance fields ================================================================================================
private String principal;
//~ Constructors ===================================================================================================
public PrincipalSid(String principal) {
Assert.hasText(principal, "Principal required");
this.principal = principal;
}
public PrincipalSid(Authentication authentication) {
Assert.notNull(authentication, "Authentication required");
Assert.notNull(authentication.getPrincipal(), "Principal required");
if (authentication.getPrincipal() instanceof UserDetails) {
this.principal = ((UserDetails) authentication.getPrincipal()).getUsername();
} else {
this.principal = authentication.getPrincipal().toString();
}
}
//~ Methods ========================================================================================================
public boolean equals(Object object) {
if ((object == null) || !(object instanceof PrincipalSid)) {
return false;
}
// Delegate to getPrincipal() to perform actual comparison (both should be identical)
return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal());
}
public int hashCode() {
return this.getPrincipal().hashCode();
}
public String getPrincipal() {
return principal;
}
public String toString() {
return "PrincipalSid[" + this.principal + "]";
}
}
@@ -1,50 +0,0 @@
/* 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.springframework.security.acls.sid;
/**
* A security identity recognised by the ACL system.
*
* <p>
* This interface provides indirection between actual security objects (eg
* principals, roles, groups etc) and what is stored inside an
* <code>Acl</code>. This is because an <code>Acl</code> will not store an
* entire security object, but only an abstraction of it. This interface
* therefore provides a simple way to compare these abstracted security
* identities with other security identities and actual security objects.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface Sid {
//~ Methods ========================================================================================================
/**
* Refer to the <code>java.lang.Object</code> documentation for the interface contract.
*
* @param obj to be compared
*
* @return <code>true</code> if the objects are equal, <code>false</code> otherwise
*/
boolean equals(Object obj);
/**
* Refer to the <code>java.lang.Object</code> documentation for the interface contract.
*
* @return a hash code representation of this object
*/
int hashCode();
}
@@ -1,32 +0,0 @@
/* 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.springframework.security.acls.sid;
import org.springframework.security.Authentication;
/**
* Strategy interface that provides an ability to determine the {@link Sid} instances applicable
* for an {@link Authentication}.
*
* @author Ben Alex
* @version $Id$
*/
public interface SidRetrievalStrategy {
//~ Methods ========================================================================================================
Sid[] getSids(Authentication authentication);
}
@@ -1,45 +0,0 @@
/* 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.springframework.security.acls.sid;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
/**
* Basic implementation of {@link SidRetrievalStrategy} that creates a {@link Sid} for the principal, as well as
* every granted authority the principal holds.
* <p>
* The returned array will always contain the {@link PrincipalSid} before any {@link GrantedAuthoritySid} elements.
*
* @author Ben Alex
* @version $Id$
*/
public class SidRetrievalStrategyImpl implements SidRetrievalStrategy {
//~ Methods ========================================================================================================
public Sid[] getSids(Authentication authentication) {
GrantedAuthority[] authorities = authentication.getAuthorities();
Sid[] sids = new Sid[authorities.length + 1];
sids[0] = new PrincipalSid(authentication);
for (int i = 1; i <= authorities.length; i++) {
sids[i] = new GrantedAuthoritySid(authorities[i - 1]);
}
return sids;
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Provides indirection between ACL packages and security identities, such as principals and GrantedAuthority[]s.
</body>
</html>
@@ -1,126 +0,0 @@
/* 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.springframework.security.afterinvocation;
import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AclService;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategy;
import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.acls.sid.SidRetrievalStrategy;
import org.springframework.security.acls.sid.SidRetrievalStrategyImpl;
import org.springframework.util.Assert;
/**
* DOCUMENT ME!
*
* @author $author$
* @version $Revision$
*/
public abstract class AbstractAclProvider implements AfterInvocationProvider {
//~ Instance fields ================================================================================================
private AclService aclService;
private Class processDomainObjectClass = Object.class;
private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
private String processConfigAttribute;
private Permission[] requirePermission = {BasePermission.READ};
//~ Constructors ===================================================================================================
public AbstractAclProvider(AclService aclService, String processConfigAttribute, Permission[] requirePermission) {
Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
Assert.notNull(aclService, "An AclService is mandatory");
if ((requirePermission == null) || (requirePermission.length == 0)) {
throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
}
this.aclService = aclService;
this.processConfigAttribute = processConfigAttribute;
this.requirePermission = requirePermission;
}
//~ Methods ========================================================================================================
protected Class getProcessDomainObjectClass() {
return processDomainObjectClass;
}
protected boolean hasPermission(Authentication authentication, Object domainObject) {
// Obtain the OID applicable to the domain object
ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);
// Obtain the SIDs applicable to the principal
Sid[] sids = sidRetrievalStrategy.getSids(authentication);
Acl acl = null;
try {
// Lookup only ACLs for SIDs we're interested in
acl = aclService.readAclById(objectIdentity, sids);
return acl.isGranted(requirePermission, sids, false);
} catch (NotFoundException ignore) {
return false;
}
}
public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required");
this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
}
protected void setProcessConfigAttribute(String processConfigAttribute) {
Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
this.processConfigAttribute = processConfigAttribute;
}
public void setProcessDomainObjectClass(Class processDomainObjectClass) {
Assert.notNull(processDomainObjectClass, "processDomainObjectClass cannot be set to null");
this.processDomainObjectClass = processDomainObjectClass;
}
public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
this.sidRetrievalStrategy = sidRetrievalStrategy;
}
public boolean supports(ConfigAttribute attribute) {
return processConfigAttribute.equals(attribute.getAttribute());
}
/**
* This implementation supports any type of class, because it does not query the presented secure object.
*
* @param clazz the secure object
*
* @return always <code>true</code>
*/
public boolean supports(Class clazz) {
return true;
}
}
@@ -1,135 +0,0 @@
/* 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.springframework.security.afterinvocation;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.AuthorizationServiceException;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.acls.AclService;
import org.springframework.security.acls.Permission;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Collection;
import java.util.Iterator;
/**
* <p>
* Given a <code>Collection</code> of domain object instances returned from a secure object invocation, remove
* any <code>Collection</code> elements the principal does not have appropriate permission to access as defined by the
* {@link AclService}.
* <p>
* The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with
* each <code>Collection</code> domain object instance element for the current <code>Authentication</code> object.
* <p>
* This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} matches the {@link
* #processConfigAttribute}. The provider will then lookup the ACLs from the <code>AclService</code> and ensure the
* principal is {@link org.springframework.security.acls.Acl#isGranted(org.springframework.security.acls.Permission[],
* org.springframework.security.acls.sid.Sid[], boolean) Acl.isGranted(Permission[], Sid[], boolean)}
* when presenting the {@link #requirePermission} array to that method.
* <p>
* If the principal does not have permission, that element will not be included in the returned
* <code>Collection</code>.
* <p>
* Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code> with a {@link
* #processConfigAttribute} of <code>AFTER_ACL_COLLECTION_READ</code> and a {@link #requirePermission} of
* <code>BasePermission.READ</code>. These are also the defaults.
* <p>
* If the provided <code>returnObject</code> is <code>null</code>, a <code>null</code><code>Collection</code>
* will be returned. If the provided <code>returnObject</code> is not a <code>Collection</code>, an {@link
* AuthorizationServiceException} will be thrown.
* <p>
* All comparisons and prefixes are case sensitive.
*
* @author Ben Alex
* @author Paulo Neves
* @version $Id$
*/
public class AclEntryAfterInvocationCollectionFilteringProvider extends AbstractAclProvider {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationCollectionFilteringProvider.class);
//~ Constructors ===================================================================================================
public AclEntryAfterInvocationCollectionFilteringProvider(AclService aclService, Permission[] requirePermission) {
super(aclService, "AFTER_ACL_COLLECTION_READ", requirePermission);
}
//~ Methods ========================================================================================================
public Object decide(Authentication authentication, Object object, ConfigAttributeDefinition config,
Object returnedObject) throws AccessDeniedException {
if (returnedObject == null) {
if (logger.isDebugEnabled()) {
logger.debug("Return object is null, skipping");
}
return null;
}
Iterator iter = config.getConfigAttributes().iterator();
while (iter.hasNext()) {
ConfigAttribute attr = (ConfigAttribute) iter.next();
if (!this.supports(attr)) {
continue;
}
// Need to process the Collection for this invocation
Filterer filterer;
if (returnedObject instanceof Collection) {
filterer = new CollectionFilterer((Collection) returnedObject);
} else if (returnedObject.getClass().isArray()) {
filterer = new ArrayFilterer((Object[]) returnedObject);
} else {
throw new AuthorizationServiceException("A Collection or an array (or null) was required as the "
+ "returnedObject, but the returnedObject was: " + returnedObject);
}
// Locate unauthorised Collection elements
Iterator collectionIter = filterer.iterator();
while (collectionIter.hasNext()) {
Object domainObject = collectionIter.next();
// Ignore nulls or entries which aren't instances of the configured domain object class
if (domainObject == null || !getProcessDomainObjectClass().isAssignableFrom(domainObject.getClass())) {
continue;
}
if(!hasPermission(authentication, domainObject)) {
filterer.remove(domainObject);
if (logger.isDebugEnabled()) {
logger.debug("Principal is NOT authorised for element: " + domainObject);
}
}
}
return filterer.getFilteredObject();
}
return returnedObject;
}
}
@@ -1,125 +0,0 @@
/* 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.springframework.security.afterinvocation;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.acls.AclService;
import org.springframework.security.acls.Permission;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import java.util.Iterator;
/**
* Given a domain object instance returned from a secure object invocation, ensures the principal has
* appropriate permission as defined by the {@link AclService}.
* <p>
* The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with a
* domain object instance for the current <code>Authentication</code> object.
* <p>
* This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} matches the {@link
* #processConfigAttribute}. The provider will then lookup the ACLs from the <tt>AclService</tt> and ensure the
* principal is {@link org.springframework.security.acls.Acl#isGranted(org.springframework.security.acls.Permission[],
org.springframework.security.acls.sid.Sid[], boolean) Acl.isGranted(Permission[], Sid[], boolean)}
* when presenting the {@link #requirePermission} array to that method.
* <p>
* Often users will setup an <code>AclEntryAfterInvocationProvider</code> with a {@link
* #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a {@link #requirePermission} of
* <code>BasePermission.READ</code>. These are also the defaults.
* <p>
* If the principal does not have sufficient permissions, an <code>AccessDeniedException</code> will be thrown.
* <p>
* If the provided <tt>returnedObject</tt> is <code>null</code>, permission will always be granted and
* <code>null</code> will be returned.
* <p>
* All comparisons and prefixes are case sensitive.
*/
public class AclEntryAfterInvocationProvider extends AbstractAclProvider implements MessageSourceAware {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationProvider.class);
//~ Instance fields ================================================================================================
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//~ Constructors ===================================================================================================
public AclEntryAfterInvocationProvider(AclService aclService, Permission[] requirePermission) {
super(aclService, "AFTER_ACL_READ", requirePermission);
}
//~ Methods ========================================================================================================
public Object decide(Authentication authentication, Object object, ConfigAttributeDefinition config,
Object returnedObject) throws AccessDeniedException {
Iterator iter = config.getConfigAttributes().iterator();
if (returnedObject == null) {
// AclManager interface contract prohibits nulls
// As they have permission to null/nothing, grant access
if (logger.isDebugEnabled()) {
logger.debug("Return object is null, skipping");
}
return null;
}
if (!getProcessDomainObjectClass().isAssignableFrom(returnedObject.getClass())) {
if (logger.isDebugEnabled()) {
logger.debug("Return object is not applicable for this provider, skipping");
}
return returnedObject;
}
while (iter.hasNext()) {
ConfigAttribute attr = (ConfigAttribute) iter.next();
if (!this.supports(attr)) {
continue;
}
// Need to make an access decision on this invocation
if (hasPermission(authentication, returnedObject)) {
return returnedObject;
}
logger.debug("Denying access");
throw new AccessDeniedException(messages.getMessage("BasicAclEntryAfterInvocationProvider.noPermission",
new Object[] {authentication.getName(), returnedObject},
"Authentication {0} has NO permissions to the domain object {1}"));
}
return returnedObject;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
}
@@ -1,63 +0,0 @@
/* 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.
*/
/*
* Copyright (c) 2005 Your Corporation. All Rights Reserved.
*/
package org.springframework.security.captcha;
/**
* <p>return false if ny CaptchaChannelProcessorTemplate of mapped urls has been requested more than thresold; <br>
* Default keyword : REQUIRES_CAPTCHA_ABOVE_THRESOLD_REQUESTS</p>
*
* @author Marc-Antoine Garrigue
* @version $Id$
*/
public class AlwaysTestAfterMaxRequestsCaptchaChannelProcessor extends CaptchaChannelProcessorTemplate {
//~ Static fields/initializers =====================================================================================
/** Keyword for this channelProcessor */
public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_ABOVE_THRESHOLD_REQUESTS";
//~ Constructors ===================================================================================================
/**
* Constructor
*/
public AlwaysTestAfterMaxRequestsCaptchaChannelProcessor() {
this.setKeyword(DEFAULT_KEYWORD);
}
//~ Methods ========================================================================================================
/**
* Verify whether the context is valid concerning humanity
*
* @param context
*
* @return true if valid, false otherwise
*/
boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
if (context.getHumanRestrictedResourcesRequestsCount() < getThreshold()) {
logger.debug("context is valid : request count < thresold");
return true;
} else {
logger.debug("context is not valid : request count > thresold");
return false;
}
}
}
@@ -1,61 +0,0 @@
/* 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.springframework.security.captcha;
/**
* <p>return false if thresold is greater than millis since last captcha test has occured;<br>
* Default keyword : REQUIRES_CAPTCHA_AFTER_THRESOLD_IN_MILLIS</p>
*
* @author Marc-Antoine Garrigue
* @version $Id$
*/
public class AlwaysTestAfterTimeInMillisCaptchaChannelProcessor extends CaptchaChannelProcessorTemplate {
//~ Static fields/initializers =====================================================================================
/** Keyword for this channelProcessor */
public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_AFTER_THRESHOLD_IN_MILLIS";
//~ Constructors ===================================================================================================
/**
* Constructor
*/
public AlwaysTestAfterTimeInMillisCaptchaChannelProcessor() {
this.setKeyword(DEFAULT_KEYWORD);
}
//~ Methods ========================================================================================================
/**
* Verify wheter the context is valid concerning humanity
*
* @param context the CaptchaSecurityContext
*
* @return true if valid, false otherwise
*/
boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
if ((System.currentTimeMillis() - context.getLastPassedCaptchaDateInMillis()) < getThreshold()) {
logger.debug("context is valid : last passed captcha date - current time < thresold");
return true;
} else {
logger.debug("context is not valid : last passed captcha date - current time > thresold");
return false;
}
}
}
@@ -1,86 +0,0 @@
/* 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.springframework.security.captcha;
import org.springframework.util.Assert;
/**
* <p>return false if thresold is lower than average time millis between any CaptchaChannelProcessorTemplate mapped
* urls requests and is human;<br>
* Default keyword : REQUIRES_CAPTCHA_BELOW_AVERAGE_TIME_IN_MILLIS_REQUESTS <br>
* Note : before first humanity check</p>
*
* @author Marc-Antoine Garrigue
* @version $Id$
*/
public class AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor extends CaptchaChannelProcessorTemplate {
//~ Static fields/initializers =====================================================================================
/** Keyword for this channelProcessor */
public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_BELOW_AVERAGE_TIME_IN_MILLIS_REQUESTS";
//~ Constructors ===================================================================================================
/**
* Constructor
*/
public AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor() {
this.setKeyword(DEFAULT_KEYWORD);
}
//~ Methods ========================================================================================================
/**
* Verify if thresold is &gt; 0
*
* @throws Exception if false
*/
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.isTrue(getThreshold() > 0, "thresold must be > 0");
}
/**
* Verify wheter the context is valid concerning humanity
*
* @param context
*
* @return true if valid, false otherwise
*/
boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
int req = context.getHumanRestrictedResourcesRequestsCount();
float thresold = getThreshold();
float duration = System.currentTimeMillis() - context.getLastPassedCaptchaDateInMillis();
float average;
if (req == 0) {
average = thresold + 1;
} else {
average = duration / req;
}
if (context.isHuman() && (average > thresold)) {
logger.debug("context is valid : average time between requests < thresold && is human");
return true;
} else {
logger.debug("context is not valid : request count > thresold or is not human");
return false;
}
}
}
@@ -1,152 +0,0 @@
/* 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.springframework.security.captcha;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.securechannel.ChannelEntryPoint;
import org.springframework.security.securechannel.ChannelProcessor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import java.util.Iterator;
import javax.servlet.ServletException;
/**
* <p>CaptchaChannel template : Ensures the user has enough human privileges by review of the {@link
* CaptchaSecurityContext} and using an abstract routine {@link
* #isContextValidConcerningHumanity(CaptchaSecurityContext)} (implemented by sub classes)</p>
* <P>The component uses 2 main parameters for its configuration :
* <ul>
* <li>a keyword to be mapped to urls in the {@link
* org.springframework.security.securechannel.ChannelProcessingFilter} configuration<br>
* default value provided by sub classes.</li>
* <li>and a thresold : used by the routine {@link
* #isContextValidConcerningHumanity(CaptchaSecurityContext)} to evaluate whether the {@link
* CaptchaSecurityContext} is valid default value = 0</li>
* </ul>
* </p>
*
* @author marc antoine Garrigue
* @version $Id$
*/
public abstract class CaptchaChannelProcessorTemplate implements ChannelProcessor, InitializingBean {
//~ Instance fields ================================================================================================
private ChannelEntryPoint entryPoint;
protected Log logger = LogFactory.getLog(this.getClass());
private String keyword = null;
private int thresold = 0;
//~ Methods ========================================================================================================
/**
* Verify if entryPoint and keyword are ok
*
* @throws Exception if not
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(entryPoint, "entryPoint required");
Assert.hasLength(keyword, "keyword required");
}
public void decide(FilterInvocation invocation, ConfigAttributeDefinition config)
throws IOException, ServletException {
if ((invocation == null) || (config == null)) {
throw new IllegalArgumentException("Nulls cannot be provided");
}
CaptchaSecurityContext context = null;
context = (CaptchaSecurityContext) SecurityContextHolder.getContext();
Iterator iter = config.getConfigAttributes().iterator();
while (iter.hasNext()) {
ConfigAttribute attribute = (ConfigAttribute) iter.next();
if (supports(attribute)) {
logger.debug("supports this attribute : " + attribute);
if (!isContextValidConcerningHumanity(context)) {
logger.debug("context is not allowed to access ressource, redirect to captcha entry point");
redirectToEntryPoint(invocation);
} else {
logger.debug("has been successfully checked this keyword, increment request count");
context.incrementHumanRestrictedResourcesRequestsCount();
}
} else {
logger.debug("do not support this attribute");
}
}
}
public ChannelEntryPoint getEntryPoint() {
return entryPoint;
}
public String getKeyword() {
return keyword;
}
public int getThreshold() {
return thresold;
}
abstract boolean isContextValidConcerningHumanity(CaptchaSecurityContext context);
private void redirectToEntryPoint(FilterInvocation invocation)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("context is not valid : redirecting to entry point");
}
entryPoint.commence(invocation.getRequest(), invocation.getResponse());
}
public void setEntryPoint(ChannelEntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public void setThreshold(int thresold) {
this.thresold = thresold;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute != null) && (keyword.equals(attribute.getAttribute()))) {
return true;
} else {
return false;
}
}
}
@@ -1,397 +0,0 @@
/* 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.springframework.security.captcha;
import org.springframework.security.securechannel.ChannelEntryPoint;
import org.springframework.security.util.PortMapper;
import org.springframework.security.util.PortMapperImpl;
import org.springframework.security.util.PortResolver;
import org.springframework.security.util.PortResolverImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* The captcha entry point : redirect to the captcha test page.
* <p>
* This entry point can force the use of SSL : see {@link #getForceHttps()}
* </p>
* <p>
* This entry point allows internal OR external redirect : see {@link #setOutsideWebApp(boolean)}<br />
* / Original request can be added to the redirect path using a custom translation : see
* {@link #setIncludeOriginalRequest(boolean)}<br />
* The original request is translated using URLEncoding and the following translation mapping in the redirect url :
* <ul>
* <li>original url => {@link #getOriginalRequestUrlParameterName()}</li>
* <li>If {@link #isIncludeOriginalParameters()}</li>
* <li>original method => {@link #getOriginalRequestMethodParameterName()}</li>
* <li>original parameters => {@link #getOriginalRequestParametersParameterName()}</li>
* <li>The original parameters string is contructed using :
* <ul>
* <li>a parameter separator {@link #getOriginalRequestParametersSeparator()}</li>
* <li>a parameter name value pair separator for each parameter {@link
* #getOriginalRequestParametersNameValueSeparator()}</li>
* </ul>
* </li>
* </ul>
* <br><br>
* Default values :
* <pre>
* forceHttps = false
* includesOriginalRequest = true
* includesOriginalParameters = false
* isOutsideWebApp = false
* originalRequestUrlParameterName = original_requestUrl
* originalRequestParametersParameterName = original_request_parameters
* originalRequestParametersNameValueSeparator = __
* originalRequestParametersSeparator = ;;
* originalRequestMethodParameterName = original_request_method
* urlEncodingCharset = UTF-8
* </pre>
* </p>
*
* @author marc antoine Garrigue
* @version $Id$
*/
public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(CaptchaEntryPoint.class);
//~ Instance fields ================================================================================================
private PortMapper portMapper = new PortMapperImpl();
private PortResolver portResolver = new PortResolverImpl();
private String captchaFormUrl;
private String originalRequestMethodParameterName = "original_request_method";
private String originalRequestParametersNameValueSeparator = "__";
private String originalRequestParametersParameterName = "original_request_parameters";
private String originalRequestParametersSeparator = ";;";
private String originalRequestUrlParameterName = "original_requestUrl";
private String urlEncodingCharset = "UTF-8";
private boolean forceHttps = false;
private boolean includeOriginalParameters = false;
private boolean includeOriginalRequest = true;
private boolean isOutsideWebApp = false;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasLength(captchaFormUrl, "captchaFormUrl must be specified");
Assert.hasLength(originalRequestMethodParameterName, "originalRequestMethodParameterName must be specified");
Assert.hasLength(originalRequestParametersNameValueSeparator,
"originalRequestParametersNameValueSeparator must be specified");
Assert.hasLength(originalRequestParametersParameterName,
"originalRequestParametersParameterName must be specified");
Assert.hasLength(originalRequestParametersSeparator, "originalRequestParametersSeparator must be specified");
Assert.hasLength(originalRequestUrlParameterName, "originalRequestUrlParameterName must be specified");
Assert.hasLength(urlEncodingCharset, "urlEncodingCharset must be specified");
Assert.notNull(portMapper, "portMapper must be specified");
Assert.notNull(portResolver, "portResolver must be specified");
URLEncoder.encode(" fzaef é& à ", urlEncodingCharset);
}
private void buildInternalRedirect(StringBuffer redirectUrl, HttpServletRequest req) {
// construct it
StringBuffer simpleRedirect = new StringBuffer();
String scheme = req.getScheme();
String serverName = req.getServerName();
int serverPort = portResolver.getServerPort(req);
String contextPath = req.getContextPath();
boolean includePort = true;
if ("http".equals(scheme.toLowerCase()) && (serverPort == 80)) {
includePort = false;
}
if ("https".equals(scheme.toLowerCase()) && (serverPort == 443)) {
includePort = false;
}
simpleRedirect.append(scheme);
simpleRedirect.append("://");
simpleRedirect.append(serverName);
if (includePort) {
simpleRedirect.append(":");
simpleRedirect.append(serverPort);
}
simpleRedirect.append(contextPath);
simpleRedirect.append(captchaFormUrl);
if (forceHttps && req.getScheme().equals("http")) {
Integer httpPort = new Integer(portResolver.getServerPort(req));
Integer httpsPort = (Integer) portMapper.lookupHttpsPort(httpPort);
if (httpsPort != null) {
if (httpsPort.intValue() == 443) {
includePort = false;
} else {
includePort = true;
}
redirectUrl.append("https://");
redirectUrl.append(serverName);
if (includePort) {
redirectUrl.append(":");
redirectUrl.append(httpsPort);
}
redirectUrl.append(contextPath);
redirectUrl.append(captchaFormUrl);
} else {
redirectUrl.append(simpleRedirect);
}
} else {
redirectUrl.append(simpleRedirect);
}
}
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
StringBuffer redirectUrl = new StringBuffer();
HttpServletRequest req = (HttpServletRequest) request;
if (isOutsideWebApp) {
redirectUrl = redirectUrl.append(captchaFormUrl);
} else {
buildInternalRedirect(redirectUrl, req);
}
if (includeOriginalRequest) {
includeOriginalRequest(redirectUrl, req);
}
// add post parameter? DONE!
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to: " + redirectUrl);
}
((HttpServletResponse) response).sendRedirect(redirectUrl.toString());
}
/**
*
* @return the captcha test page to redirect to.
*/
public String getCaptchaFormUrl() {
return captchaFormUrl;
}
public boolean getForceHttps() {
return forceHttps;
}
public String getOriginalRequestMethodParameterName() {
return originalRequestMethodParameterName;
}
public String getOriginalRequestParametersNameValueSeparator() {
return originalRequestParametersNameValueSeparator;
}
public String getOriginalRequestParametersParameterName() {
return originalRequestParametersParameterName;
}
public String getOriginalRequestParametersSeparator() {
return originalRequestParametersSeparator;
}
public String getOriginalRequestUrlParameterName() {
return originalRequestUrlParameterName;
}
public PortMapper getPortMapper() {
return portMapper;
}
public PortResolver getPortResolver() {
return portResolver;
}
public String getUrlEncodingCharset() {
return urlEncodingCharset;
}
private void includeOriginalRequest(StringBuffer redirectUrl, HttpServletRequest req) {
// add original request to the url
if (redirectUrl.indexOf("?") >= 0) {
redirectUrl.append("&");
} else {
redirectUrl.append("?");
}
redirectUrl.append(originalRequestUrlParameterName);
redirectUrl.append("=");
try {
redirectUrl.append(URLEncoder.encode(req.getRequestURL().toString(), urlEncodingCharset));
} catch (UnsupportedEncodingException e) {
logger.warn(e);
}
//append method
redirectUrl.append("&");
redirectUrl.append(originalRequestMethodParameterName);
redirectUrl.append("=");
redirectUrl.append(req.getMethod());
if (includeOriginalParameters) {
// append query params
redirectUrl.append("&");
redirectUrl.append(originalRequestParametersParameterName);
redirectUrl.append("=");
StringBuffer qp = new StringBuffer();
Enumeration parameters = req.getParameterNames();
if ((parameters != null) && parameters.hasMoreElements()) {
//qp.append("?");
while (parameters.hasMoreElements()) {
String name = parameters.nextElement().toString();
String value = req.getParameter(name);
qp.append(name);
qp.append(originalRequestParametersNameValueSeparator);
qp.append(value);
if (parameters.hasMoreElements()) {
qp.append(originalRequestParametersSeparator);
}
}
}
try {
redirectUrl.append(URLEncoder.encode(qp.toString(), urlEncodingCharset));
} catch (Exception e) {
logger.warn(e);
}
}
}
public boolean isIncludeOriginalParameters() {
return includeOriginalParameters;
}
public boolean isIncludeOriginalRequest() {
return includeOriginalRequest;
}
public boolean isOutsideWebApp() {
return isOutsideWebApp;
}
/**
* The URL where the <code>CaptchaProcessingFilter</code> login page can be found. Should be relative to
* the web-app context path, and include a leading <code>/</code>
*
* @param captchaFormUrl
*/
public void setCaptchaFormUrl(String captchaFormUrl) {
this.captchaFormUrl = captchaFormUrl;
}
// ~ Methods
// ================================================================
/**
* Set to true to force captcha form access to be via https. If this value is ture (the default is false),
* and the incoming request for the protected resource which triggered the interceptor was not already
* <code>https</code>, then
*
* @param forceHttps
*/
public void setForceHttps(boolean forceHttps) {
this.forceHttps = forceHttps;
}
public void setIncludeOriginalParameters(boolean includeOriginalParameters) {
this.includeOriginalParameters = includeOriginalParameters;
}
/**
* If set to true, the original request url will be appended to the redirect url using the {@link
* #getOriginalRequestUrlParameterName()}.
*
* @param includeOriginalRequest
*/
public void setIncludeOriginalRequest(boolean includeOriginalRequest) {
this.includeOriginalRequest = includeOriginalRequest;
}
public void setOriginalRequestMethodParameterName(String originalRequestMethodParameterName) {
this.originalRequestMethodParameterName = originalRequestMethodParameterName;
}
public void setOriginalRequestParametersNameValueSeparator(String originalRequestParametersNameValueSeparator) {
this.originalRequestParametersNameValueSeparator = originalRequestParametersNameValueSeparator;
}
public void setOriginalRequestParametersParameterName(String originalRequestParametersParameterName) {
this.originalRequestParametersParameterName = originalRequestParametersParameterName;
}
public void setOriginalRequestParametersSeparator(String originalRequestParametersSeparator) {
this.originalRequestParametersSeparator = originalRequestParametersSeparator;
}
public void setOriginalRequestUrlParameterName(String originalRequestUrlParameterName) {
this.originalRequestUrlParameterName = originalRequestUrlParameterName;
}
/**
* if set to true, the {@link #commence(ServletRequest, ServletResponse)} method uses the {@link
* #getCaptchaFormUrl()} as a complete URL, else it as a 'inside WebApp' path.
*
* @param isOutsideWebApp
*/
public void setOutsideWebApp(boolean isOutsideWebApp) {
this.isOutsideWebApp = isOutsideWebApp;
}
public void setPortMapper(PortMapper portMapper) {
this.portMapper = portMapper;
}
public void setPortResolver(PortResolver portResolver) {
this.portResolver = portResolver;
}
public void setUrlEncodingCharset(String urlEncodingCharset) {
this.urlEncodingCharset = urlEncodingCharset;
}
}
@@ -1,59 +0,0 @@
/* 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.springframework.security.captcha;
import org.springframework.security.context.SecurityContext;
/**
* Interface that add humanity concerns to the SecurityContext
*
* @author marc antoine garrigue
*/
public interface CaptchaSecurityContext extends SecurityContext {
//~ Methods ========================================================================================================
/**
* DOCUMENT ME!
*
* @return number of human restricted resources requests since the last passed captcha.
*/
int getHumanRestrictedResourcesRequestsCount();
/**
* DOCUMENT ME!
*
* @return the date of the last passed Captcha in millis, 0 if the user never passed captcha.
*/
long getLastPassedCaptchaDateInMillis();
/**
* Method to increment the human Restricted Resrouces Requests Count;
*/
void incrementHumanRestrictedResourcesRequestsCount();
/**
* DOCUMENT ME!
*
* @return true if the current user has already passed a captcha.
*/
boolean isHuman();
/**
* set human attribute, should called after captcha validation.
*/
void setHuman();
}
@@ -1,105 +0,0 @@
/* 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.springframework.security.captcha;
import org.springframework.security.context.SecurityContextImpl;
/**
* Default CaptchaSecurityContext implementation
*
* @author mag
*/
public class CaptchaSecurityContextImpl extends SecurityContextImpl implements CaptchaSecurityContext {
//~ Instance fields ================================================================================================
private boolean human;
private int humanRestrictedResourcesRequestsCount;
private long lastPassedCaptchaDate;
//~ Constructors ===================================================================================================
public CaptchaSecurityContextImpl() {
super();
human = false;
lastPassedCaptchaDate = 0;
humanRestrictedResourcesRequestsCount = 0;
}
//~ Methods ========================================================================================================
public boolean equals(Object obj) {
if (obj instanceof CaptchaSecurityContextImpl) {
CaptchaSecurityContextImpl rhs = (CaptchaSecurityContextImpl) obj;
if (this.isHuman() != rhs.isHuman()) {
return false;
}
if (this.getHumanRestrictedResourcesRequestsCount() != rhs.getHumanRestrictedResourcesRequestsCount()) {
return false;
}
if (this.getLastPassedCaptchaDateInMillis() != rhs.getLastPassedCaptchaDateInMillis()) {
return false;
}
return super.equals(obj);
}
return false;
}
public int getHumanRestrictedResourcesRequestsCount() {
return humanRestrictedResourcesRequestsCount;
}
public long getLastPassedCaptchaDateInMillis() {
return lastPassedCaptchaDate;
}
public int hashCode() {
int code = super.hashCode();
code ^= this.humanRestrictedResourcesRequestsCount;
code ^= this.lastPassedCaptchaDate;
if (this.isHuman()) {
code ^= -37;
}
return code;
}
/**
* Method to increment the human Restricted Resrouces Requests Count;
*/
public void incrementHumanRestrictedResourcesRequestsCount() {
humanRestrictedResourcesRequestsCount++;
}
public boolean isHuman() {
return human;
}
/**
* Reset the lastPassedCaptchaDate and count.
*/
public void setHuman() {
this.human = true;
this.lastPassedCaptchaDate = System.currentTimeMillis();
this.humanRestrictedResourcesRequestsCount = 0;
}
}
@@ -1,36 +0,0 @@
/* 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.springframework.security.captcha;
/**
* Provide a common interface for captcha validation.
*
* @author marc antoine Garrigue
* @version $Id$
*/
public interface CaptchaServiceProxy {
//~ Methods ========================================================================================================
/**
* DOCUMENT ME!
*
* @param id the id token
* @param captchaResponse the user response
*
* @return true if the response is validated by the back end captcha service.
*/
boolean validateReponseForId(String id, Object captchaResponse);
}
@@ -1,137 +0,0 @@
/* 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.springframework.security.captcha;
import org.springframework.security.context.SecurityContextHolder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* Filter for web integration of the {@link CaptchaServiceProxy}. <br>
* It basically intercept calls containing the specific validation parameter, use the {@link CaptchaServiceProxy} to
* validate the request, and update the {@link CaptchaSecurityContext} if the request passed the validation. <br>
* This Filter should be placed after the ContextIntegration filter and before the {@link
* CaptchaChannelProcessorTemplate} filter in the filter stack in order to update the {@link CaptchaSecurityContext}
* before the humanity verification routine occurs. <br>
* This filter should only be used in conjunction with the {@link CaptchaSecurityContext}<br>
*
* @author marc antoine Garrigue
* @version $Id$
*/
public class CaptchaValidationProcessingFilter implements InitializingBean, Filter {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(CaptchaValidationProcessingFilter.class);
//~ Instance fields ================================================================================================
private CaptchaServiceProxy captchaService;
private String captchaValidationParameter = "_captcha_parameter";
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
if (this.captchaService == null) {
throw new IllegalArgumentException("CaptchaServiceProxy must be defined ");
}
if ((this.captchaValidationParameter == null) || "".equals(captchaValidationParameter)) {
throw new IllegalArgumentException("captchaValidationParameter must not be empty or null");
}
}
/**
* Does nothing. We use IoC container lifecycle services instead.
*/
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String captchaResponse = request.getParameter(captchaValidationParameter);
if ((request != null) && request instanceof HttpServletRequest && (captchaResponse != null)) {
logger.debug("captcha validation parameter found");
// validate the request against CaptchaServiceProxy
boolean valid = false;
logger.debug("try to validate");
//get session
HttpSession session = ((HttpServletRequest) request).getSession();
if (session != null) {
String id = session.getId();
valid = this.captchaService.validateReponseForId(id, captchaResponse);
logger.debug("captchaServiceProxy says : request is valid = " + valid);
if (valid) {
logger.debug("update the context");
((CaptchaSecurityContext) SecurityContextHolder.getContext()).setHuman();
//logger.debug("retrieve original request from ")
} else {
logger.debug("captcha test failed");
}
} else {
logger.debug("no session found, user don't even ask a captcha challenge");
}
} else {
logger.debug("captcha validation parameter not found, do nothing");
}
if (logger.isDebugEnabled()) {
logger.debug("chain ...");
}
chain.doFilter(request, response);
}
public CaptchaServiceProxy getCaptchaService() {
return captchaService;
}
public String getCaptchaValidationParameter() {
return captchaValidationParameter;
}
/**
* Does nothing. We use IoC container lifecycle services instead.
*
* @param filterConfig ignored
*
* @throws ServletException ignored
*/
public void init(FilterConfig filterConfig) throws ServletException {}
public void setCaptchaService(CaptchaServiceProxy captchaService) {
this.captchaService = captchaService;
}
public void setCaptchaValidationParameter(String captchaValidationParameter) {
this.captchaValidationParameter = captchaValidationParameter;
}
}
@@ -1,50 +0,0 @@
/* 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.springframework.security.captcha;
/**
* <p>return false if ny CaptchaChannelProcessorTemplate mapped urls has been requested more than thresold and
* humanity is false; <br>
* Default keyword : REQUIRES_CAPTCHA_ONCE_ABOVE_THRESOLD_REQUESTS</p>
*
* @author Marc-Antoine Garrigue
* @version $Id$
*/
public class TestOnceAfterMaxRequestsCaptchaChannelProcessor extends CaptchaChannelProcessorTemplate {
//~ Static fields/initializers =====================================================================================
public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_ONCE_ABOVE_THRESOLD_REQUESTS";
//~ Constructors ===================================================================================================
public TestOnceAfterMaxRequestsCaptchaChannelProcessor() {
this.setKeyword(DEFAULT_KEYWORD);
}
//~ Methods ========================================================================================================
boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
if (context.isHuman() || (context.getHumanRestrictedResourcesRequestsCount() < getThreshold())) {
logger.debug("context is valid concerning humanity or request count < thresold");
return true;
} else {
logger.debug("context is not valid concerning humanity and request count > thresold");
return false;
}
}
}
@@ -1,12 +0,0 @@
<html>
<body>
Captcha classes. Contains :<br/>
<ul>
<li>a CaptchaSecurityContext that overrides the default SecurityContext and holds some captcha related informations</li>
<li>an abstract CaptchaChannelProcessorTemplate and its implementations that test this context according to the configuration</li>
<li>a CaptchaServiceProxy and a CaptchaValidationProcessingFilter that alows to validate a captcha response and to update the CaptchaSecurity</li>
<li>a CaptchaEntryPoint that redirects to a captcha page if the CaptchaChannelProcessor implementation decide so</li>
</ul>
</body>
</html>
@@ -0,0 +1,38 @@
package org.springframework.security.intercept.web;
/**
* @author Luke Taylor
* @version $Id$
*/
public class RequestKey {
String url;
String method;
public RequestKey(String url) {
this(url, "");
}
public RequestKey(String url, String method) {
this.url = url;
this.method = method;
}
public int hashCode() {
int code = 31;
code ^= url.hashCode();
code ^= method.hashCode();
return code;
}
public boolean equals(Object obj) {
if (!(obj instanceof RequestKey)) {
return false;
}
RequestKey key = (RequestKey) obj;
return url.equals(key.url) && method.equals(key.method);
}
}
@@ -26,11 +26,9 @@ import org.springframework.security.CredentialsExpiredException;
import org.springframework.security.DisabledException;
import org.springframework.security.LockedException;
import org.springframework.security.AccountStatusException;
import org.springframework.security.concurrent.ConcurrentLoginException;
import org.springframework.security.concurrent.ConcurrentSessionController;
import org.springframework.security.concurrent.NullConcurrentSessionController;
import org.springframework.security.event.authentication.AbstractAuthenticationEvent;
import org.springframework.security.event.authentication.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.event.authentication.AuthenticationFailureConcurrentLoginEvent;
@@ -42,25 +40,20 @@ import org.springframework.security.event.authentication.AuthenticationFailurePr
import org.springframework.security.event.authentication.AuthenticationFailureProxyUntrustedEvent;
import org.springframework.security.event.authentication.AuthenticationFailureServiceExceptionEvent;
import org.springframework.security.event.authentication.AuthenticationSuccessEvent;
import org.springframework.security.providers.cas.ProxyUntrustedException;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -140,7 +133,7 @@ public class ProviderManager extends AbstractAuthenticationManager implements In
AuthenticationFailureConcurrentLoginEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(ProviderNotFoundException.class.getName(),
AuthenticationFailureProviderNotFoundEvent.class.getName());
DEFAULT_EXCEPTION_MAPPINGS.put(ProxyUntrustedException.class.getName(),
DEFAULT_EXCEPTION_MAPPINGS.put("org.springframework.security.providers.cas.ProxyUntrustedException",
AuthenticationFailureProxyUntrustedEvent.class.getName());
}
@@ -338,16 +331,15 @@ public class ProviderManager extends AbstractAuthenticationManager implements In
applicationEventPublisher.publishEvent(event);
}
}
/**
* Sets additional exception to event mappings. These are automatically merged with the default
* exception to event mappings that <code>ProviderManager</code> defines.
*
* @param additionalExceptionMappings where keys are the fully-qualified string name of the
* exception class and the values are the fully-qualified string name of the event class to fire
*
* @param additionalExceptionMappings where keys are the fully-qualified string name of the exception class and the
* values are the fully-qualified string name of the event class to fire.
*/
public void setAdditionalExceptionMappings(
Properties additionalExceptionMappings) {
this.additionalExceptionMappings = additionalExceptionMappings;
}
public void setAdditionalExceptionMappings(Properties additionalExceptionMappings) {
this.additionalExceptionMappings = additionalExceptionMappings;
}
}
@@ -1,208 +0,0 @@
/* 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.springframework.security.providers.cas;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.providers.AuthenticationProvider;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.cas.cache.NullStatelessTicketCache;
import org.springframework.security.ui.cas.CasProcessingFilter;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UserDetailsChecker;
import org.springframework.security.userdetails.checker.AccountStatusUserDetailsChecker;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
/**
* An {@link AuthenticationProvider} implementation that integrates with JA-SIG Central Authentication Service
* (CAS).
* <p>
* This <code>AuthenticationProvider</code> is capable of validating {@link UsernamePasswordAuthenticationToken}
* requests which contain a <code>principal</code> name equal to either
* {@link CasProcessingFilter#CAS_STATEFUL_IDENTIFIER} or {@link CasProcessingFilter#CAS_STATELESS_IDENTIFIER}.
* It can also validate a previously created {@link CasAuthenticationToken}.
*
* @author Ben Alex
* @version $Id$
*/
public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
//~ Instance fields ================================================================================================
private UserDetailsService userDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private CasProxyDecider casProxyDecider;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache();
private String key;
private TicketValidator ticketValidator;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.userDetailsService, "A userDetailsService must be set");
Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
Assert.notNull(this.casProxyDecider, "A casProxyDecider must be set");
Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
Assert.hasText(this.key, "A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
Assert.notNull(this.messages, "A message source must be set");
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!supports(authentication.getClass())) {
return null;
}
if (authentication instanceof UsernamePasswordAuthenticationToken
&& (!CasProcessingFilter.CAS_STATEFUL_IDENTIFIER.equals(authentication.getPrincipal().toString())
&& !CasProcessingFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal().toString()))) {
// UsernamePasswordAuthenticationToken not CAS related
return null;
}
// If an existing CasAuthenticationToken, just check we created it
if (authentication instanceof CasAuthenticationToken) {
if (this.key.hashCode() == ((CasAuthenticationToken) authentication).getKeyHash()) {
return authentication;
} else {
throw new BadCredentialsException(messages.getMessage("CasAuthenticationProvider.incorrectKey",
"The presented CasAuthenticationToken does not contain the expected key"));
}
}
// Ensure credentials are presented
if ((authentication.getCredentials() == null) || "".equals(authentication.getCredentials())) {
throw new BadCredentialsException(messages.getMessage("CasAuthenticationProvider.noServiceTicket",
"Failed to provide a CAS service ticket to validate"));
}
boolean stateless = false;
if (authentication instanceof UsernamePasswordAuthenticationToken
&& CasProcessingFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal())) {
stateless = true;
}
CasAuthenticationToken result = null;
if (stateless) {
// Try to obtain from cache
result = statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
}
if (result == null) {
result = this.authenticateNow(authentication);
result.setDetails(authentication.getDetails());
}
if (stateless) {
// Add to cache
statelessTicketCache.putTicketInCache(result);
}
return result;
}
private CasAuthenticationToken authenticateNow(Authentication authentication) throws AuthenticationException {
// Validate
TicketResponse response = ticketValidator.confirmTicketValid(authentication.getCredentials().toString());
// Check proxy list is trusted
this.casProxyDecider.confirmProxyListTrusted(response.getProxyList());
// Lookup user details
UserDetails userDetails = userDetailsService.loadUserByUsername(response.getUser());
userDetailsChecker.check(userDetails);
// Construct CasAuthenticationToken
return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(),
userDetails.getAuthorities(), userDetails, response.getProxyList(), response.getProxyGrantingTicketIou());
}
protected UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public CasProxyDecider getCasProxyDecider() {
return casProxyDecider;
}
public void setCasProxyDecider(CasProxyDecider casProxyDecider) {
this.casProxyDecider = casProxyDecider;
}
protected String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public StatelessTicketCache getStatelessTicketCache() {
return statelessTicketCache;
}
protected TicketValidator getTicketValidator() {
return ticketValidator;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setStatelessTicketCache(StatelessTicketCache statelessTicketCache) {
this.statelessTicketCache = statelessTicketCache;
}
public void setTicketValidator(TicketValidator ticketValidator) {
this.ticketValidator = ticketValidator;
}
public boolean supports(Class authentication) {
if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)) {
return true;
} else if (CasAuthenticationToken.class.isAssignableFrom(authentication)) {
return true;
} else {
return false;
}
}
}
@@ -1,157 +0,0 @@
/* 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.springframework.security.providers.cas;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.providers.AbstractAuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import java.io.Serializable;
import java.util.List;
/**
* Represents a successful CAS <code>Authentication</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
//~ Instance fields ================================================================================================
private static final long serialVersionUID = 1L;
private final List proxyList;
private final Object credentials;
private final Object principal;
private final String proxyGrantingTicketIou;
private final UserDetails userDetails;
private final int keyHash;
//~ Constructors ===================================================================================================
/**
* Constructor.
*
* @param key to identify if this object made by a given {@link
* CasAuthenticationProvider}
* @param principal typically the UserDetails object (cannot be <code>null</code>)
* @param credentials the service/proxy ticket ID from CAS (cannot be
* <code>null</code>)
* @param authorities the authorities granted to the user (from the {@link
* org.springframework.security.userdetails.UserDetailsService}) (cannot be <code>null</code>)
* @param userDetails the user details (from the {@link
* org.springframework.security.userdetails.UserDetailsService}) (cannot be <code>null</code>)
* @param proxyList the list of proxies from CAS (cannot be
* <code>null</code>)
* @param proxyGrantingTicketIou the PGT-IOU ID from CAS (cannot be
* <code>null</code>, but may be an empty <code>String</code> if no
* PGT-IOU ID was provided)
*
* @throws IllegalArgumentException if a <code>null</code> was passed
*/
public CasAuthenticationToken(final String key, final Object principal, final Object credentials,
final GrantedAuthority[] authorities, final UserDetails userDetails, final List proxyList,
final String proxyGrantingTicketIou) {
super(authorities);
if ((key == null) || ("".equals(key)) || (principal == null) || "".equals(principal) || (credentials == null)
|| "".equals(credentials) || (authorities == null) || (userDetails == null) || (proxyList == null)
|| (proxyGrantingTicketIou == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
this.keyHash = key.hashCode();
this.principal = principal;
this.credentials = credentials;
this.userDetails = userDetails;
this.proxyList = proxyList;
this.proxyGrantingTicketIou = proxyGrantingTicketIou;
setAuthenticated(true);
}
//~ Methods ========================================================================================================
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
if (obj instanceof CasAuthenticationToken) {
CasAuthenticationToken test = (CasAuthenticationToken) obj;
// proxyGrantingTicketIou is never null due to constructor
if (!this.getProxyGrantingTicketIou().equals(test.getProxyGrantingTicketIou())) {
return false;
}
// proxyList is never null due to constructor
if (!this.getProxyList().equals(test.getProxyList())) {
return false;
}
if (this.getKeyHash() != test.getKeyHash()) {
return false;
}
return true;
}
return false;
}
public Object getCredentials() {
return this.credentials;
}
public int getKeyHash() {
return this.keyHash;
}
public Object getPrincipal() {
return this.principal;
}
/**
* Obtains the proxy granting ticket IOU.
*
* @return the PGT IOU-ID or an empty <code>String</code> if no proxy callback was requested when validating the
* service ticket
*/
public String getProxyGrantingTicketIou() {
return proxyGrantingTicketIou;
}
public List getProxyList() {
return proxyList;
}
public UserDetails getUserDetails() {
return userDetails;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(super.toString());
sb.append("; Credentials (Service/Proxy Ticket): ").append(this.credentials);
sb.append("; Proxy-Granting Ticket IOU: ").append(this.proxyGrantingTicketIou);
sb.append("; Proxy List: ").append(this.proxyList);
return (sb.toString());
}
}
@@ -1,73 +0,0 @@
/* 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.springframework.security.providers.cas;
import java.util.List;
/**
* Decides whether a proxy list presented via CAS is trusted or not.
*
* <p>
* CAS 1.0 allowed services to receive a service ticket and then validate it.
* CAS 2.0 allows services to receive a service ticket and then validate it
* with a proxy callback URL. The callback will enable the CAS server to
* authenticate the service. In doing so the service will receive a
* proxy-granting ticket and a proxy-granting ticket IOU. The IOU is just an
* internal record that a proxy-granting ticket is due to be received via the
* callback URL.
* </p>
*
* <p>
* With a proxy-granting ticket, a service can request the CAS server provides
* it with a proxy ticket. A proxy ticket is just a service ticket, but the
* CAS server internally tracks the list (chain) of services used to build the
* proxy ticket. The proxy ticket is then presented to the target service.
* </p>
*
* <p>
* If this application is a target service of a proxy ticket, the
* <code>CasProxyDecider</code> resolves whether or not the proxy list is
* trusted. Applications should only trust services they allow to impersonate
* an end user.
* </p>
*
* <p>
* If this application is a service that should never accept proxy-granting
* tickets, the implementation should reject tickets that present a proxy list
* with any members. If the list has no members, it indicates the CAS server
* directly authenticated the user (ie there are no services which proxied the
* user authentication).
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface CasProxyDecider {
//~ Methods ========================================================================================================
/**
* Decides whether the proxy list is trusted.
* <p>Must throw any <code>ProxyUntrustedException</code> if the
* proxy list is untrusted.</p>
*
* @param proxyList the list of proxies to be checked.
*
* @throws ProxyUntrustedException DOCUMENT ME!
*/
void confirmProxyListTrusted(List proxyList)
throws ProxyUntrustedException;
}
@@ -1,50 +0,0 @@
/* 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.springframework.security.providers.cas;
import org.springframework.security.AuthenticationException;
/**
* Thrown if a CAS proxy ticket is presented from an untrusted proxy.
*
* @author Ben Alex
* @version $Id$
*/
public class ProxyUntrustedException extends AuthenticationException {
//~ Constructors ===================================================================================================
/**
* Constructs a <code>ProxyUntrustedException</code> with the specified
* message.
*
* @param msg the detail message.
*/
public ProxyUntrustedException(String msg) {
super(msg);
}
/**
* Constructs a <code>ProxyUntrustedException</code> with the specified
* message and root cause.
*
* @param msg the detail message.
* @param t root cause
*/
public ProxyUntrustedException(String msg, Throwable t) {
super(msg, t);
}
}
@@ -1,116 +0,0 @@
/* Copyright 2004 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.springframework.security.providers.cas;
/**
* Caches CAS service tickets and CAS proxy tickets for stateless connections.
*
* <p>
* When a service ticket or proxy ticket is validated against the CAS server,
* it is unable to be used again. Most types of callers are stateful and are
* associated with a given <code>HttpSession</code>. This allows the
* affirmative CAS validation outcome to be stored in the
* <code>HttpSession</code>, meaning the removal of the ticket from the CAS
* server is not an issue.
* </p>
*
* <P>
* Stateless callers, such as remoting protocols, cannot take advantage of
* <code>HttpSession</code>. If the stateless caller is located a significant
* network distance from the CAS server, acquiring a fresh service ticket or
* proxy ticket for each invocation would be expensive.
* </p>
*
* <P>
* To avoid this issue with stateless callers, it is expected stateless callers
* will obtain a single service ticket or proxy ticket, and then present this
* same ticket to the Spring Security secured application on each
* occasion. As no <code>HttpSession</code> is available for such callers, the
* affirmative CAS validation outcome cannot be stored in this location.
* </p>
*
* <P>
* The <code>StatelessTicketCache</code> enables the service tickets and proxy
* tickets belonging to stateless callers to be placed in a cache. This
* in-memory cache stores the <code>CasAuthenticationToken</code>, effectively
* providing the same capability as a <code>HttpSession</code> with the ticket
* identifier being the key rather than a session identifier.
* </p>
*
* <P>
* Implementations should provide a reasonable timeout on stored entries, such
* that the stateless caller are not required to unnecessarily acquire fresh
* CAS service tickets or proxy tickets.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface StatelessTicketCache {
//~ Methods ================================================================
/**
* Retrieves the <code>CasAuthenticationToken</code> associated with the
* specified ticket.
*
* <P>
* If not found, returns a
* <code>null</code><code>CasAuthenticationToken</code>.
* </p>
*
* @return the fully populated authentication token
*/
CasAuthenticationToken getByTicketId(String serviceTicket);
/**
* Adds the specified <code>CasAuthenticationToken</code> to the cache.
*
* <P>
* The {@link CasAuthenticationToken#getCredentials()} method is used to
* retrieve the service ticket number.
* </p>
*
* @param token to be added to the cache
*/
void putTicketInCache(CasAuthenticationToken token);
/**
* Removes the specified ticket from the cache, as per {@link
* #removeTicketFromCache(String)}.
*
* <P>
* Implementations should use {@link
* CasAuthenticationToken#getCredentials()} to obtain the ticket and then
* delegate to to the {@link #removeTicketFromCache(String)} method.
* </p>
*
* @param token to be removed
*/
void removeTicketFromCache(CasAuthenticationToken token);
/**
* Removes the specified ticket from the cache, meaning that future calls
* will require a new service ticket.
*
* <P>
* This is in case applications wish to provide a session termination
* capability for their stateless clients.
* </p>
*
* @param serviceTicket to be removed
*/
void removeTicketFromCache(String serviceTicket);
}
@@ -1,96 +0,0 @@
/* 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.springframework.security.providers.cas;
import java.util.List;
import java.util.Vector;
/**
* Represents a CAS service ticket in native CAS form.
*
* @author Ben Alex
* @version $Id$
*/
public class TicketResponse {
//~ Instance fields ================================================================================================
private List proxyList;
private String proxyGrantingTicketIou;
private String user;
//~ Constructors ===================================================================================================
/**
* Constructor.
*
* <P>
* If <code>null</code> is passed into the <code>proxyList</code> or
* <code>proxyGrantingTicketIou</code>, suitable defaults are established.
* However, <code>null</code> cannot be passed for the <code>user</code>
* argument.
* </p>
*
* @param user the user as indicated by CAS (cannot be <code>null</code> or
* an empty <code>String</code>)
* @param proxyList as provided by CAS (may be <code>null</code>)
* @param proxyGrantingTicketIou as provided by CAS (may be
* <code>null</code>)
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public TicketResponse(String user, List proxyList, String proxyGrantingTicketIou) {
if (proxyList == null) {
proxyList = new Vector();
}
if (proxyGrantingTicketIou == null) {
proxyGrantingTicketIou = "";
}
if ((user == null) || "".equals(user)) {
throw new IllegalArgumentException("Cannot pass null or empty String for User");
}
this.user = user;
this.proxyList = proxyList;
this.proxyGrantingTicketIou = proxyGrantingTicketIou;
}
//~ Methods ========================================================================================================
public String getProxyGrantingTicketIou() {
return proxyGrantingTicketIou;
}
public List getProxyList() {
return proxyList;
}
public String getUser() {
return user;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(super.toString());
sb.append(": User: " + this.user);
sb.append("; Proxy-Granting Ticket IOU: " + this.proxyGrantingTicketIou);
sb.append("; Proxy List: " + this.proxyList.toString());
return sb.toString();
}
}
@@ -1,53 +0,0 @@
/* 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.springframework.security.providers.cas;
import org.springframework.security.AuthenticationException;
/**
* Validates a CAS service ticket.
*
* <p>
* Implementations must accept CAS proxy tickets, in addition to CAS service
* tickets. If proxy tickets should be rejected, this is resolved by a {@link
* CasProxyDecider} implementation (not by the <code>TicketValidator</code>).
* </p>
*
* <p>
* Implementations may request a proxy granting ticket if wish, although this
* behaviour is not mandatory.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface TicketValidator {
//~ Methods ========================================================================================================
/**
* Returns information about the ticket, if it is valid for this service.<P>Must throw an
* <code>AuthenticationException</code> if the ticket is not valid for this service.</p>
*
* @param serviceTicket DOCUMENT ME!
*
* @return details of the CAS service ticket
*
* @throws AuthenticationException DOCUMENT ME!
*/
TicketResponse confirmTicketValid(String serviceTicket)
throws AuthenticationException;
}
@@ -1,105 +0,0 @@
/* 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.springframework.security.providers.cas.cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import net.sf.ehcache.Ehcache;
import org.springframework.security.providers.cas.CasAuthenticationToken;
import org.springframework.security.providers.cas.StatelessTicketCache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.util.Assert;
/**
* Caches tickets using a Spring IoC defined <A HREF="http://ehcache.sourceforge.net">EHCACHE</a>.
*
* @author Ben Alex
* @version $Id$
*/
public class EhCacheBasedTicketCache implements StatelessTicketCache, InitializingBean {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(EhCacheBasedTicketCache.class);
//~ Instance fields ================================================================================================
private Ehcache cache;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "cache mandatory");
}
public CasAuthenticationToken getByTicketId(String serviceTicket) {
Element element = null;
try {
element = cache.get(serviceTicket);
} catch (CacheException cacheException) {
throw new DataRetrievalFailureException("Cache failure: " + cacheException.getMessage());
}
if (logger.isDebugEnabled()) {
logger.debug("Cache hit: " + (element != null) + "; service ticket: " + serviceTicket);
}
if (element == null) {
return null;
} else {
return (CasAuthenticationToken) element.getValue();
}
}
public Ehcache getCache() {
return cache;
}
public void putTicketInCache(CasAuthenticationToken token) {
Element element = new Element(token.getCredentials().toString(), token);
if (logger.isDebugEnabled()) {
logger.debug("Cache put: " + element.getKey());
}
cache.put(element);
}
public void removeTicketFromCache(CasAuthenticationToken token) {
if (logger.isDebugEnabled()) {
logger.debug("Cache remove: " + token.getCredentials().toString());
}
this.removeTicketFromCache(token.getCredentials().toString());
}
public void removeTicketFromCache(String serviceTicket) {
cache.remove(serviceTicket);
}
public void setCache(Ehcache cache) {
this.cache = cache;
}
}
@@ -1,63 +0,0 @@
/* 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.springframework.security.providers.cas.cache;
import org.springframework.security.providers.cas.CasAuthenticationProvider;
import org.springframework.security.providers.cas.CasAuthenticationToken;
import org.springframework.security.providers.cas.StatelessTicketCache;
/**
* Implementation of @link {@link StatelessTicketCache} that has no backing cache. Useful
* in instances where storing of tickets for stateless session management is not required.
* <p>
* This is the default StatelessTicketCache of the @link {@link CasAuthenticationProvider} to
* eliminate the unnecessary dependency on EhCache that applications have even if they are not using
* the stateless session management.
*
* @author Scott Battaglia
* @version $Id$
*
*@see CasAuthenticationProvider
*/
public final class NullStatelessTicketCache implements StatelessTicketCache {
/**
* @return null since we are not storing any tickets.
*/
public CasAuthenticationToken getByTicketId(final String serviceTicket) {
return null;
}
/**
* This is a no-op since we are not storing tickets.
*/
public void putTicketInCache(final CasAuthenticationToken token) {
// nothing to do
}
/**
* This is a no-op since we are not storing tickets.
*/
public void removeTicketFromCache(final CasAuthenticationToken token) {
// nothing to do
}
/**
* This is a no-op since we are not storing tickets.
*/
public void removeTicketFromCache(final String serviceTicket) {
// nothing to do
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Caches CAS tickets for the <code>CasAuthenticationProvider</code>.
</body>
</html>
@@ -1,6 +0,0 @@
<html>
<body>
An authentication provider that can process JA-SIG Central Authentication Service (CAS)
service tickets and proxy tickets.
</body>
</html>
@@ -1,51 +0,0 @@
/* 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.springframework.security.providers.cas.proxy;
import org.springframework.security.providers.cas.CasProxyDecider;
import org.springframework.security.providers.cas.ProxyUntrustedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import java.util.List;
/**
* Accepts a proxied request from any other service.<P>Also accepts the request if there was no proxy (ie the user
* directly authenticated against this service).</p>
*
* @author Ben Alex
* @version $Id$
*/
public class AcceptAnyCasProxy implements CasProxyDecider {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(AcceptAnyCasProxy.class);
//~ Methods ========================================================================================================
public void confirmProxyListTrusted(List proxyList)
throws ProxyUntrustedException {
Assert.notNull(proxyList, "proxyList cannot be null");
if (logger.isDebugEnabled()) {
logger.debug("Always accepting proxy list: " + proxyList.toString());
}
}
}
@@ -1,88 +0,0 @@
/* 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.springframework.security.providers.cas.proxy;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.providers.cas.CasProxyDecider;
import org.springframework.security.providers.cas.ProxyUntrustedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
import java.util.List;
/**
* Accepts proxied requests if the closest proxy is named in the <code>validProxies</code> list.<P>Also accepts the
* request if there was no proxy (ie the user directly authenticated against this service).</p>
*/
public class NamedCasProxyDecider implements CasProxyDecider, InitializingBean, MessageSourceAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(NamedCasProxyDecider.class);
//~ Instance fields ================================================================================================
private List validProxies;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.validProxies, "A validProxies list must be set");
Assert.notNull(this.messages, "A message source must be set");
}
public void confirmProxyListTrusted(List proxyList)
throws ProxyUntrustedException {
Assert.notNull(proxyList, "proxyList cannot be null");
if (logger.isDebugEnabled()) {
logger.debug("Proxy list: " + proxyList.toString());
}
if (proxyList.size() == 0) {
// A Service Ticket (not a Proxy Ticket)
return;
}
if (!validProxies.contains(proxyList.get(0))) {
throw new ProxyUntrustedException(messages.getMessage("NamedCasProxyDecider.untrusted",
new Object[] {proxyList.get(0)}, "Nearest proxy {0} is untrusted"));
}
}
public List getValidProxies() {
return validProxies;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setValidProxies(List validProxies) {
this.validProxies = validProxies;
}
}
@@ -1,76 +0,0 @@
/* 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.springframework.security.providers.cas.proxy;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.providers.cas.CasProxyDecider;
import org.springframework.security.providers.cas.ProxyUntrustedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
import java.util.List;
/**
* Accepts no proxied requests.<P>This class should be used if only service tickets wish to be accepted (ie no
* proxy tickets at all).</p>
*/
public class RejectProxyTickets implements CasProxyDecider, MessageSourceAware, InitializingBean {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(RejectProxyTickets.class);
//~ Instance fields ================================================================================================
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.messages, "A message source must be set");
}
public void confirmProxyListTrusted(List proxyList)
throws ProxyUntrustedException {
Assert.notNull(proxyList, "proxyList cannot be null");
if (proxyList.size() == 0) {
// A Service Ticket (not a Proxy Ticket)
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Proxies are unacceptable; proxy list provided: " + proxyList.toString());
}
throw new ProxyUntrustedException(
messages.getMessage("RejectProxyTickets.reject", "Proxy tickets are rejected"));
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
}
@@ -1,6 +0,0 @@
<html>
<body>
Implementations that decide whether proxy lists of
CAS authentications are trusted.
</body>
</html>
@@ -1,114 +0,0 @@
/* 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.springframework.security.providers.cas.ticketvalidator;
import org.springframework.security.providers.cas.TicketValidator;
import org.springframework.security.ui.cas.ServiceProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.io.File;
/**
* Convenience abstract base for <code>TicketValidator</code>s.
*
* @author Ben Alex
* @version $Id$
*/
public abstract class AbstractTicketValidator implements TicketValidator, InitializingBean {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(AbstractTicketValidator.class);
//~ Instance fields ================================================================================================
private ServiceProperties serviceProperties;
private String casValidate;
private String trustStore;
private String trustPassword;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasLength(casValidate, "A casValidate URL must be set");
Assert.notNull(serviceProperties, "serviceProperties must be specified");
if (StringUtils.hasLength(trustStore)) {
logger.info("Setting system property 'javax.net.ssl.trustStore' to value [" + trustStore + "]");
if (! (new File(trustStore)).exists()) {
throw new IllegalArgumentException("Parameter 'trustStore' file does not exist at " + trustStore);
}
System.setProperty("javax.net.ssl.trustStore", trustStore);
}
if (StringUtils.hasLength(trustPassword)) {
System.setProperty("javax.net.ssl.trustStorePassword", trustPassword);
}
}
/**
* Mandatory URL to CAS' proxy ticket valiation service.<P>This is usually something like
* <code>https://www.mycompany.com/cas/proxyValidate</code>.</p>
*
* @return the CAS proxy ticket validation URL
*/
public String getCasValidate() {
return casValidate;
}
public ServiceProperties getServiceProperties() {
return serviceProperties;
}
/**
* Optional property which will be used to set the system property <code>javax.net.ssl.trustStore</code>.
*
* @return the <code>javax.net.ssl.trustStore</code> that will be set during bean initialization, or
* <code>null</code> to leave the system property unchanged
*/
public String getTrustStore() {
return trustStore;
}
public void setCasValidate(String casValidate) {
this.casValidate = casValidate;
}
public void setServiceProperties(ServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
public void setTrustStore(String trustStore) {
this.trustStore = trustStore;
}
/**
* Optional property which causes the system property <tt>javax.net.ssl.trustStorePassword</tt> to be set.
*
* @param trustPassword
*/
public void setTrustPassword(String trustPassword) {
this.trustPassword = trustPassword;
}
}
@@ -1,116 +0,0 @@
/* 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.springframework.security.providers.cas.ticketvalidator;
import edu.yale.its.tp.cas.client.ProxyTicketValidator;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationServiceException;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.providers.cas.TicketResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Uses CAS' <code>ProxyTicketValidator</code> to validate a service ticket.
*
* @author Ben Alex
* @version $Id$
*/
public class CasProxyTicketValidator extends AbstractTicketValidator {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(CasProxyTicketValidator.class);
//~ Instance fields ================================================================================================
private String proxyCallbackUrl;
//~ Methods ========================================================================================================
public TicketResponse confirmTicketValid(String serviceTicket)
throws AuthenticationException {
// Attempt to validate presented ticket using CAS' ProxyTicketValidator class
ProxyTicketValidator pv = new ProxyTicketValidator();
pv.setCasValidateUrl(super.getCasValidate());
pv.setServiceTicket(serviceTicket);
pv.setService(super.getServiceProperties().getService());
if (super.getServiceProperties().isSendRenew()) {
logger.warn(
"The current CAS ProxyTicketValidator does not support the 'renew' property. "
+ "The ticket cannot be validated as having been issued by a 'renew' authentication. "
+ "It is expected this will be corrected in a future version of CAS' ProxyTicketValidator.");
}
if ((this.proxyCallbackUrl != null) && (!"".equals(this.proxyCallbackUrl))) {
pv.setProxyCallbackUrl(proxyCallbackUrl);
}
return validateNow(pv);
}
/**
* Optional callback URL to obtain a proxy-granting ticket from CAS.
* <p>This callback URL belongs to the Spring Security secured application. We suggest you use
* CAS' <code>ProxyTicketReceptor</code> servlet to receive this callback and manage the proxy-granting ticket list.
* The callback URL is usually something like
* <code>https://www.mycompany.com/application/casProxy/receptor</code>.
* </p>
* <p>If left <code>null</code>, the <code>CasAuthenticationToken</code> will not have a proxy granting
* ticket IOU and there will be no proxy-granting ticket callback. Accordingly, the Spring Securty
* secured application will be unable to obtain a proxy ticket to call another CAS-secured service on
* behalf of the user. This is not really an issue for most applications.</p>
*
* @return the proxy callback URL, or <code>null</code> if not used
*/
public String getProxyCallbackUrl() {
return proxyCallbackUrl;
}
public void setProxyCallbackUrl(String proxyCallbackUrl) {
this.proxyCallbackUrl = proxyCallbackUrl;
}
/**
* Perform the actual remote invocation. Protected to enable replacement during tests.
*
* @param pv the populated <code>ProxyTicketValidator</code>
*
* @return the <code>TicketResponse</code>
*
* @throws AuthenticationServiceException if<code>ProxyTicketValidator</code> internally fails
* @throws BadCredentialsException DOCUMENT ME!
*/
protected TicketResponse validateNow(ProxyTicketValidator pv)
throws AuthenticationServiceException, BadCredentialsException {
try {
pv.validate();
} catch (Exception internalProxyTicketValidatorProblem) {
throw new AuthenticationServiceException(internalProxyTicketValidatorProblem.getMessage());
}
if (!pv.isAuthenticationSuccesful()) {
throw new BadCredentialsException(pv.getErrorCode() + ": " + pv.getErrorMessage());
}
return new TicketResponse(pv.getUser(), pv.getProxyList(), pv.getPgtIou());
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Implementations that validate service tickets.
</body>
</html>
@@ -1,234 +0,0 @@
/* 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.springframework.security.taglibs.authz;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AclService;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategy;
import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.acls.sid.SidRetrievalStrategy;
import org.springframework.security.acls.sid.SidRetrievalStrategyImpl;
import org.springframework.security.context.SecurityContextHolder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.util.ExpressionEvaluationUtils;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.HashMap;
import javax.servlet.ServletContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
/**
* An implementation of {@link Tag} that allows its body through if some authorizations are granted to the request's
* principal.
* <p>
* One or more comma separate numeric are specified via the <tt>hasPermission</tt> attribute.
* These permissions are then converted into {@link Permission} instances. These instances are then presented as an
* array to the {@link Acl#isGranted(Permission[], org.springframework.security.acls.sid.Sid[], boolean)} method.
* The {@link Sid} presented is determined by the {@link SidRetrievalStrategy}.
* <p>
* For this class to operate it must be able to access the application context via the
* <code>WebApplicationContextUtils</code> and locate an {@link AclService} and {@link SidRetrievalStrategy}.
* Application contexts must provide one and only one of these Java types.
*
* @author Ben Alex
* @version $Id$
*/
public class AccessControlListTag extends TagSupport {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(AccessControlListTag.class);
//~ Instance fields ================================================================================================
private AclService aclService;
private ApplicationContext applicationContext;
private Object domainObject;
private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy;
private SidRetrievalStrategy sidRetrievalStrategy;
private String hasPermission = "";
//~ Methods ========================================================================================================
public int doStartTag() throws JspException {
initializeIfRequired();
if ((null == hasPermission) || "".equals(hasPermission)) {
return Tag.SKIP_BODY;
}
final String evaledPermissionsString = ExpressionEvaluationUtils.evaluateString("hasPermission", hasPermission,
pageContext);
Permission[] requiredPermissions = null;
try {
requiredPermissions = parsePermissionsString(evaledPermissionsString);
} catch (NumberFormatException nfe) {
throw new JspException(nfe);
}
Object resolvedDomainObject = null;
if (domainObject instanceof String) {
resolvedDomainObject = ExpressionEvaluationUtils.evaluate("domainObject", (String) domainObject,
Object.class, pageContext);
} else {
resolvedDomainObject = domainObject;
}
if (resolvedDomainObject == null) {
if (logger.isDebugEnabled()) {
logger.debug("domainObject resolved to null, so including tag body");
}
// Of course they have access to a null object!
return Tag.EVAL_BODY_INCLUDE;
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
if (logger.isDebugEnabled()) {
logger.debug(
"SecurityContextHolder did not return a non-null Authentication object, so skipping tag body");
}
return Tag.SKIP_BODY;
}
Sid[] sids = sidRetrievalStrategy.getSids(SecurityContextHolder.getContext().getAuthentication());
ObjectIdentity oid = objectIdentityRetrievalStrategy.getObjectIdentity(resolvedDomainObject);
// Obtain aclEntrys applying to the current Authentication object
try {
Acl acl = aclService.readAclById(oid, sids);
if (acl.isGranted(requiredPermissions, sids, false)) {
return Tag.EVAL_BODY_INCLUDE;
} else {
return Tag.SKIP_BODY;
}
} catch (NotFoundException nfe) {
return Tag.SKIP_BODY;
}
}
/**
* Allows test cases to override where application context obtained from.
*
* @param pageContext so the <code>ServletContext</code> can be accessed as required by Spring's
* <code>WebApplicationContextUtils</code>
*
* @return the Spring application context (never <code>null</code>)
*/
protected ApplicationContext getContext(PageContext pageContext) {
ServletContext servletContext = pageContext.getServletContext();
return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
}
public Object getDomainObject() {
return domainObject;
}
public String getHasPermission() {
return hasPermission;
}
private void initializeIfRequired() throws JspException {
if (applicationContext != null) {
return;
}
this.applicationContext = getContext(pageContext);
Map map = new HashMap();
ApplicationContext context = applicationContext;
while (context != null) {
map.putAll(context.getBeansOfType(AclService.class));
context = context.getParent();
}
if (map.size() != 1) {
throw new JspException(
"Found incorrect number of AclService instances in application context - you must have only have one!");
}
aclService = (AclService) map.values().iterator().next();
map = applicationContext.getBeansOfType(SidRetrievalStrategy.class);
if (map.size() == 0) {
sidRetrievalStrategy = new SidRetrievalStrategyImpl();
} else if (map.size() == 1) {
sidRetrievalStrategy = (SidRetrievalStrategy) map.values().iterator().next();
} else {
throw new JspException("Found incorrect number of SidRetrievalStrategy instances in application "
+ "context - you must have only have one!");
}
map = applicationContext.getBeansOfType(ObjectIdentityRetrievalStrategy.class);
if (map.size() == 0) {
objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
} else if (map.size() == 1) {
objectIdentityRetrievalStrategy = (ObjectIdentityRetrievalStrategy) map.values().iterator().next();
} else {
throw new JspException("Found incorrect number of ObjectIdentityRetrievalStrategy instances in "
+ "application context - you must have only have one!");
}
}
private Permission[] parsePermissionsString(String integersString)
throws NumberFormatException {
final Set permissions = new HashSet();
final StringTokenizer tokenizer;
tokenizer = new StringTokenizer(integersString, ",", false);
while (tokenizer.hasMoreTokens()) {
String integer = tokenizer.nextToken();
permissions.add(BasePermission.buildFromMask(new Integer(integer).intValue()));
}
return (Permission[]) permissions.toArray(new Permission[permissions.size()]);
}
public void setDomainObject(Object domainObject) {
this.domainObject = domainObject;
}
public void setHasPermission(String hasPermission) {
this.hasPermission = hasPermission;
}
}
@@ -1,211 +0,0 @@
/* 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.springframework.security.taglibs.authz;
import org.springframework.security.Authentication;
import org.springframework.security.acl.AclEntry;
import org.springframework.security.acl.AclManager;
import org.springframework.security.acl.basic.BasicAclEntry;
import org.springframework.security.context.SecurityContextHolder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.util.ExpressionEvaluationUtils;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
/**
* An implementation of {@link javax.servlet.jsp.tagext.Tag} that allows its body through if some authorizations
* are granted to the request's principal.<P>Only works with permissions that are subclasses of {@link
* org.springframework.security.acl.basic.BasicAclEntry}.</p>
* <p>One or more comma separate integer permissions are specified via the <code>hasPermission</code> attribute.
* The tag will include its body if <b>any</b> of the integer permissions have been granted to the current
* <code>Authentication</code> (obtained from the <code>SecurityContextHolder</code>).</p>
* <p>For this class to operate it must be able to access the application context via the
* <code>WebApplicationContextUtils</code> and locate an {@link AclManager}. Application contexts have no need to have
* more than one <code>AclManager</code> (as a provider-based implementation can be used so that it locates a provider
* that is authoritative for the given domain object instance), so the first <code>AclManager</code> located will be
* used.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class AclTag extends TagSupport {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(AclTag.class);
//~ Instance fields ================================================================================================
private Object domainObject;
private String hasPermission = "";
//~ Methods ========================================================================================================
public int doStartTag() throws JspException {
if ((null == hasPermission) || "".equals(hasPermission)) {
return Tag.SKIP_BODY;
}
final String evaledPermissionsString = ExpressionEvaluationUtils.evaluateString("hasPermission", hasPermission,
pageContext);
Integer[] requiredIntegers = null;
try {
requiredIntegers = parseIntegersString(evaledPermissionsString);
} catch (NumberFormatException nfe) {
throw new JspException(nfe);
}
Object resolvedDomainObject = null;
if (domainObject instanceof String) {
resolvedDomainObject = ExpressionEvaluationUtils.evaluate("domainObject", (String) domainObject,
Object.class, pageContext);
} else {
resolvedDomainObject = domainObject;
}
if (resolvedDomainObject == null) {
if (logger.isDebugEnabled()) {
logger.debug("domainObject resolved to null, so including tag body");
}
// Of course they have access to a null object!
return Tag.EVAL_BODY_INCLUDE;
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
if (logger.isDebugEnabled()) {
logger.debug(
"SecurityContextHolder did not return a non-null Authentication object, so skipping tag body");
}
return Tag.SKIP_BODY;
}
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
ApplicationContext context = getContext(pageContext);
String[] beans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, AclManager.class, false, false);
if (beans.length == 0) {
throw new JspException("No AclManager would found the application context: " + context.toString());
}
AclManager aclManager = (AclManager) context.getBean(beans[0]);
// Obtain aclEntrys applying to the current Authentication object
AclEntry[] acls = aclManager.getAcls(resolvedDomainObject, auth);
if (logger.isDebugEnabled()) {
logger.debug("Authentication: '" + auth + "' has: " + ((acls == null) ? 0 : acls.length)
+ " AclEntrys for domain object: '" + resolvedDomainObject + "' from AclManager: '"
+ aclManager.toString() + "'");
}
if ((acls == null) || (acls.length == 0)) {
return Tag.SKIP_BODY;
}
for (int i = 0; i < acls.length; i++) {
// Locate processable AclEntrys
if (acls[i] instanceof BasicAclEntry) {
BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
// See if principal has any of the required permissions
for (int y = 0; y < requiredIntegers.length; y++) {
if (processableAcl.isPermitted(requiredIntegers[y].intValue())) {
if (logger.isDebugEnabled()) {
logger.debug("Including tag body as found permission: " + requiredIntegers[y]
+ " due to AclEntry: '" + processableAcl + "'");
}
return Tag.EVAL_BODY_INCLUDE;
}
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("No permission, so skipping tag body");
}
return Tag.SKIP_BODY;
}
/**
* Allows test cases to override where application context obtained from.
*
* @param pageContext so the <code>ServletContext</code> can be accessed as required by Spring's
* <code>WebApplicationContextUtils</code>
*
* @return the Spring application context (never <code>null</code>)
*/
protected ApplicationContext getContext(PageContext pageContext) {
ServletContext servletContext = pageContext.getServletContext();
return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
}
public Object getDomainObject() {
return domainObject;
}
public String getHasPermission() {
return hasPermission;
}
private Integer[] parseIntegersString(String integersString)
throws NumberFormatException {
final Set integers = new HashSet();
final StringTokenizer tokenizer;
tokenizer = new StringTokenizer(integersString, ",", false);
while (tokenizer.hasMoreTokens()) {
String integer = tokenizer.nextToken();
integers.add(new Integer(integer));
}
return (Integer[]) integers.toArray(new Integer[] {});
}
public void setDomainObject(Object domainObject) {
this.domainObject = domainObject;
}
public void setHasPermission(String hasPermission) {
this.hasPermission = hasPermission;
}
}
@@ -1,135 +0,0 @@
/* 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.springframework.security.taglibs.authz;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.web.util.TagUtils;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
/**
* An {@link javax.servlet.jsp.tagext.Tag} implementation that allows convenient access to the current
* <code>Authentication</code> object. The <tt>operation</tt> attribute
* <p>
* Whilst JSPs can access the <code>SecurityContext</code> directly, this tag avoids handling <code>null</code> conditions.
*
* @author Thomas Champagne
* @version $Id$
*/
public class AuthenticationTag extends TagSupport {
//~ Instance fields ================================================================================================
private String var;
private String property;
private int scope;
private boolean scopeSpecified;
//~ Methods ========================================================================================================
public AuthenticationTag() {
init();
}
// resets local state
private void init() {
var = null;
scopeSpecified = false;
scope = PageContext.PAGE_SCOPE;
}
public void setVar(String var) {
this.var = var;
}
public void setProperty(String operation) {
this.property = operation;
}
public void setScope(String scope) {
this.scope = TagUtils.getScope(scope);
this.scopeSpecified = true;
}
public int doStartTag() throws JspException {
return super.doStartTag();
}
public int doEndTag() throws JspException {
Object result = null;
// determine the value by...
if (property != null) {
if ((SecurityContextHolder.getContext() == null)
|| !(SecurityContextHolder.getContext() instanceof SecurityContext)
|| (SecurityContextHolder.getContext().getAuthentication() == null)) {
return Tag.EVAL_PAGE;
}
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.getPrincipal() == null) {
return Tag.EVAL_PAGE;
} else {
try {
BeanWrapperImpl wrapper = new BeanWrapperImpl(auth);
result = wrapper.getPropertyValue(property);
} catch (BeansException e) {
throw new JspException(e);
}
}
}
if (var != null) {
/*
* Store the result, letting an IllegalArgumentException
* propagate back if the scope is invalid (e.g., if an attempt
* is made to store something in the session without any
* HttpSession existing).
*/
if (result != null) {
pageContext.setAttribute(var, result, scope);
} else {
if (scopeSpecified) {
pageContext.removeAttribute(var, scope);
} else {
pageContext.removeAttribute(var);
}
}
} else {
writeMessage(result.toString());
}
return EVAL_PAGE;
}
protected void writeMessage(String msg) throws JspException {
try {
pageContext.getOut().write(String.valueOf(msg));
} catch (IOException ioe) {
throw new JspException(ioe);
}
}
}
@@ -1,225 +0,0 @@
/* 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.springframework.security.taglibs.authz;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ExpressionEvaluationUtils;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
/**
* An implementation of {@link javax.servlet.jsp.tagext.Tag} that allows it's body through if some authorizations
* are granted to the request's principal.
*
* @author Francois Beausoleil
* @version $Id$
*/
public class AuthorizeTag extends TagSupport {
//~ Instance fields ================================================================================================
private String ifAllGranted = "";
private String ifAnyGranted = "";
private String ifNotGranted = "";
//~ Methods ========================================================================================================
private Set authoritiesToRoles(Collection c) {
Set target = new HashSet();
for (Iterator iterator = c.iterator(); iterator.hasNext();) {
GrantedAuthority authority = (GrantedAuthority) iterator.next();
if (null == authority.getAuthority()) {
throw new IllegalArgumentException(
"Cannot process GrantedAuthority objects which return null from getAuthority() - attempting to process "
+ authority.toString());
}
target.add(authority.getAuthority());
}
return target;
}
public int doStartTag() throws JspException {
if (((null == ifAllGranted) || "".equals(ifAllGranted)) && ((null == ifAnyGranted) || "".equals(ifAnyGranted))
&& ((null == ifNotGranted) || "".equals(ifNotGranted))) {
return Tag.SKIP_BODY;
}
final Collection granted = getPrincipalAuthorities();
final String evaledIfNotGranted = ExpressionEvaluationUtils.evaluateString("ifNotGranted", ifNotGranted,
pageContext);
if ((null != evaledIfNotGranted) && !"".equals(evaledIfNotGranted)) {
Set grantedCopy = retainAll(granted, parseAuthoritiesString(evaledIfNotGranted));
if (!grantedCopy.isEmpty()) {
return Tag.SKIP_BODY;
}
}
final String evaledIfAllGranted = ExpressionEvaluationUtils.evaluateString("ifAllGranted", ifAllGranted,
pageContext);
if ((null != evaledIfAllGranted) && !"".equals(evaledIfAllGranted)) {
if (!granted.containsAll(parseAuthoritiesString(evaledIfAllGranted))) {
return Tag.SKIP_BODY;
}
}
final String evaledIfAnyGranted = ExpressionEvaluationUtils.evaluateString("ifAnyGranted", ifAnyGranted,
pageContext);
if ((null != evaledIfAnyGranted) && !"".equals(evaledIfAnyGranted)) {
Set grantedCopy = retainAll(granted, parseAuthoritiesString(evaledIfAnyGranted));
if (grantedCopy.isEmpty()) {
return Tag.SKIP_BODY;
}
}
return Tag.EVAL_BODY_INCLUDE;
}
public String getIfAllGranted() {
return ifAllGranted;
}
public String getIfAnyGranted() {
return ifAnyGranted;
}
public String getIfNotGranted() {
return ifNotGranted;
}
private Collection getPrincipalAuthorities() {
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
if (null == currentUser) {
return Collections.EMPTY_LIST;
}
if ((null == currentUser.getAuthorities()) || (currentUser.getAuthorities().length < 1)) {
return Collections.EMPTY_LIST;
}
Collection granted = Arrays.asList(currentUser.getAuthorities());
return granted;
}
private Set parseAuthoritiesString(String authorizationsString) {
final Set requiredAuthorities = new HashSet();
final String[] authorities = StringUtils.commaDelimitedListToStringArray(authorizationsString);
for (int i = 0; i < authorities.length; i++) {
String authority = authorities[i];
// Remove the role's whitespace characters without depending on JDK 1.4+
// Includes space, tab, new line, carriage return and form feed.
String role = authority.trim(); // trim, don't use spaces, as per SEC-378
role = StringUtils.deleteAny(role, "\t\n\r\f");
requiredAuthorities.add(new GrantedAuthorityImpl(role));
}
return requiredAuthorities;
}
/**
* Find the common authorities between the current authentication's {@link GrantedAuthority} and the ones
* that have been specified in the tag's ifAny, ifNot or ifAllGranted attributes.<p>We need to manually
* iterate over both collections, because the granted authorities might not implement {@link
* Object#equals(Object)} and {@link Object#hashCode()} in the same way as {@link GrantedAuthorityImpl}, thereby
* invalidating {@link Collection#retainAll(java.util.Collection)} results.</p>
* <p>
* <strong>CAVEAT</strong>: This method <strong>will not</strong> work if the granted authorities
* returns a <code>null</code> string as the return value of {@link
* org.springframework.security.GrantedAuthority#getAuthority()}.
* </p>
* <p>Reported by rawdave, on Fri Feb 04, 2005 2:11 pm in the Spring Security forum.</p>
*
* @param granted The authorities granted by the authentication. May be any implementation of {@link
* GrantedAuthority} that does <strong>not</strong> return <code>null</code> from {@link
* org.springframework.security.GrantedAuthority#getAuthority()}.
* @param required A {@link Set} of {@link GrantedAuthorityImpl}s that have been built using ifAny, ifAll or
* ifNotGranted.
*
* @return A set containing only the common authorities between <var>granted</var> and <var>required</var>.
*
* @see <a href="http://forum.springframework.org/viewtopic.php?t=3367">authz:authorize ifNotGranted not behaving
* as expected</a> TODO: wrong article Url
*/
private Set retainAll(final Collection granted, final Set required) {
Set grantedRoles = authoritiesToRoles(granted);
Set requiredRoles = authoritiesToRoles(required);
grantedRoles.retainAll(requiredRoles);
return rolesToAuthorities(grantedRoles, granted);
}
private Set rolesToAuthorities(Set grantedRoles, Collection granted) {
Set target = new HashSet();
for (Iterator iterator = grantedRoles.iterator(); iterator.hasNext();) {
String role = (String) iterator.next();
for (Iterator grantedIterator = granted.iterator(); grantedIterator.hasNext();) {
GrantedAuthority authority = (GrantedAuthority) grantedIterator.next();
if (authority.getAuthority().equals(role)) {
target.add(authority);
break;
}
}
}
return target;
}
public void setIfAllGranted(String ifAllGranted) throws JspException {
this.ifAllGranted = ifAllGranted;
}
public void setIfAnyGranted(String ifAnyGranted) throws JspException {
this.ifAnyGranted = ifAnyGranted;
}
public void setIfNotGranted(String ifNotGranted) throws JspException {
this.ifNotGranted = ifNotGranted;
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Java implementation of security taglib.
</body>
</html>
@@ -1,5 +0,0 @@
<html>
<body>
Provides security related taglibs that can be used in JSPs.
</body>
</html>
@@ -1,102 +0,0 @@
/* 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.springframework.security.taglibs.velocity;
import org.springframework.security.Authentication;
import org.springframework.security.acl.AclManager;
import org.springframework.security.taglibs.authz.AclTag;
import org.springframework.security.taglibs.authz.AuthenticationTag;
import org.springframework.security.taglibs.authz.AuthorizeTag;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.context.ApplicationContext;
/**
* Wrapper the implementation of Spring Security JSP tag includes:
* {@link AuthenticationTag}, {@link AclTag}, {@link AuthorizeTag}
*
* @author Wang Qi
* @version $Id$
*/
public interface Authz {
//~ Methods ========================================================================================================
/**
* all the listed roles must be granted to return true, otherwise fasle;
*
* @param roles - comma separate GrantedAuthoritys
*
* @return granted (true|false)
*/
boolean allGranted(String roles);
/**
* any the listed roles must be granted to return true, otherwise fasle;
*
* @param roles - comma separate GrantedAuthoritys
*
* @return granted (true|false)
*/
boolean anyGranted(String roles);
/**
* set Spring application context which contains acegi related bean
*
* @return DOCUMENT ME!
*/
ApplicationContext getAppCtx();
/**
* return the principal's name, supports the various type of principals that can exist in the {@link
* Authentication} object, such as a String or {@link UserDetails} instance
*
* @return string representation of principal's name
*/
String getPrincipal();
/**
* return true if the principal holds either permission specified for the provided domain object<P>Only
* works with permissions that are subclasses of {@link org.springframework.security.acl.basic.AbstractBasicAclEntry}.</p>
* <p>For this class to operate it must be able to access the application context via the
* <code>WebApplicationContextUtils</code> and locate an {@link AclManager}.</p>
*
* @param domainObject - domain object need acl control
* @param permissions - comma separate integer permissions
*
* @return got acl permission (true|false)
*/
boolean hasPermission(Object domainObject, String permissions);
/**
* none the listed roles must be granted to return true, otherwise fasle;
*
* @param roles - comma separate GrantedAuthoritys
*
* @return granted (true|false)
*/
boolean noneGranted(String roles);
/**
* get Spring application context which contains acegi related bean
*
* @param appCtx DOCUMENT ME!
*/
void setAppCtx(ApplicationContext appCtx);
}
@@ -1,212 +0,0 @@
/* 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.springframework.security.taglibs.velocity;
import org.springframework.security.acl.AclManager;
import org.springframework.security.taglibs.authz.AclTag;
import org.springframework.security.taglibs.authz.AuthenticationTag;
import org.springframework.security.taglibs.authz.AuthorizeTag;
import org.springframework.context.ApplicationContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
/**
* I decided to wrap several JSP tag in one class, so I have to using inner class to wrap these JSP tag. To using
* this class, you need to inject Spring Context via SetAppCtx() method. AclTag need Spring Context to get AclManger
* bean.
*/
public class AuthzImpl implements Authz {
//~ Static fields/initializers =====================================================================================
static final int ALL_GRANTED = 1;
static final int ANY_GRANTED = 2;
static final int NONE_GRANTED = 3;
//~ Instance fields ================================================================================================
private ApplicationContext appCtx;
//~ Methods ========================================================================================================
public boolean allGranted(String roles) {
return ifGranted(roles, ALL_GRANTED);
}
public boolean anyGranted(String roles) {
return ifGranted(roles, ANY_GRANTED);
}
public ApplicationContext getAppCtx() {
return appCtx;
}
/**
* implementation of AuthenticationTag
*
* @return DOCUMENT ME!
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public String getPrincipal() {
MyAuthenticationTag authenticationTag = new MyAuthenticationTag();
authenticationTag.setProperty("username");
try {
authenticationTag.doStartTag();
} catch (JspException je) {
je.printStackTrace();
throw new IllegalArgumentException(je.getMessage());
}
return authenticationTag.getLastMessage();
}
/**
* implementation of AclTag
*
* @param domainObject DOCUMENT ME!
* @param permissions DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public boolean hasPermission(Object domainObject, String permissions) {
MyAclTag aclTag = new MyAclTag();
aclTag.setPageContext(null);
aclTag.setContext(getAppCtx());
aclTag.setDomainObject(domainObject);
aclTag.setHasPermission(permissions);
int result = -1;
try {
result = aclTag.doStartTag();
} catch (JspException je) {
throw new IllegalArgumentException(je.getMessage());
}
if (Tag.EVAL_BODY_INCLUDE == result) {
return true;
} else {
return false;
}
}
/**
* implementation of AuthorizeTag
*
* @param roles DOCUMENT ME!
* @param grantType DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
private boolean ifGranted(String roles, int grantType) {
AuthorizeTag authorizeTag = new AuthorizeTag();
int result = -1;
try {
switch (grantType) {
case ALL_GRANTED:
authorizeTag.setIfAllGranted(roles);
break;
case ANY_GRANTED:
authorizeTag.setIfAnyGranted(roles);
break;
case NONE_GRANTED:
authorizeTag.setIfNotGranted(roles);
break;
default:
throw new IllegalArgumentException("invalid granted type : " + grantType + " role=" + roles);
}
result = authorizeTag.doStartTag();
} catch (JspException je) {
throw new IllegalArgumentException(je.getMessage());
}
if (Tag.EVAL_BODY_INCLUDE == result) {
return true;
} else {
return false;
}
}
public boolean noneGranted(String roles) {
return ifGranted(roles, NONE_GRANTED);
}
/**
* test case can use this class to mock application context with aclManager bean in it.
*
* @param appCtx DOCUMENT ME!
*/
public void setAppCtx(ApplicationContext appCtx) {
this.appCtx = appCtx;
}
//~ Inner Classes ==================================================================================================
/**
* AclTag need to access the application context via the <code> WebApplicationContextUtils</code> and
* locate an {@link AclManager}. WebApplicationContextUtils get application context via ServletContext. I decided
* to let the Authz provide the Spring application context.
*/
private class MyAclTag extends AclTag {
private static final long serialVersionUID = 6752340622125924108L;
ApplicationContext context;
protected ApplicationContext getContext(PageContext pageContext) {
return context;
}
protected void setContext(ApplicationContext context) {
this.context = context;
}
}
/**
* it must output somthing to JSP page, so have to override the writeMessage method to avoid JSP related
* operation. Get Idea from Acegi Test class.
*/
private class MyAuthenticationTag extends AuthenticationTag {
private static final long serialVersionUID = -1094246833893599161L;
String lastMessage = null;
public String getLastMessage() {
return lastMessage;
}
protected void writeMessage(String msg) throws JspException {
lastMessage = msg;
}
}
}
@@ -1,243 +0,0 @@
/* 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.springframework.security.vote;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import org.springframework.security.Authentication;
import org.springframework.security.AuthorizationServiceException;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AclService;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategy;
import org.springframework.security.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.acls.sid.SidRetrievalStrategy;
import org.springframework.security.acls.sid.SidRetrievalStrategyImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* <p>
* Given a domain object instance passed as a method argument, ensures the principal has appropriate permission
* as indicated by the {@link AclService}.
* <p>
* The <tt>AclService</tt> is used to retrieve the access control list (ACL) permissions associated with a
* domain object instance for the current <tt>Authentication</tt> object.
* <p>
* The voter will vote if any {@link ConfigAttribute#getAttribute()} matches the {@link #processConfigAttribute}.
* The provider will then locate the first method argument of type {@link #processDomainObjectClass}. Assuming that
* method argument is non-null, the provider will then lookup the ACLs from the <code>AclManager</code> and ensure the
* principal is {@link Acl#isGranted(org.springframework.security.acls.Permission[],
* org.springframework.security.acls.sid.Sid[], boolean)} when presenting the {@link #requirePermission} array to that
* method.
* <p>
* If the method argument is <tt>null</tt>, the voter will abstain from voting. If the method argument
* could not be found, an {@link org.springframework.security.AuthorizationServiceException} will be thrown.
* <p>
* In practical terms users will typically setup a number of <tt>AclEntryVoter</tt>s. Each will have a
* different {@link #processDomainObjectClass}, {@link #processConfigAttribute} and {@link #requirePermission}
* combination. For example, a small application might employ the following instances of <tt>AclEntryVoter</tt>:
* <ul>
* <li>Process domain object class <code>BankAccount</code>, configuration attribute
* <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission <code>BasePermission.READ</code></li>
* <li>Process domain object class <code>BankAccount</code>, configuration attribute
* <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
* <code>BasePermission.CREATE</code> (allowing the principal to have <b>either</b> of these two permissions)</li>
* <li>Process domain object class <code>Customer</code>, configuration attribute
* <code>VOTE_ACL_CUSTOMER_READ</code>, require permission <code>BasePermission.READ</code></li>
* <li>Process domain object class <code>Customer</code>, configuration attribute
* <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
* <code>BasePermission.CREATE</code></li>
* </ul>
* Alternatively, you could have used a common superclass or interface for the {@link #processDomainObjectClass}
* if both <code>BankAccount</code> and <code>Customer</code> had common parents.</p>
* <p>If the principal does not have sufficient permissions, the voter will vote to deny access.</p>
* <p>All comparisons and prefixes are case sensitive.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class AclEntryVoter extends AbstractAclVoter {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(AclEntryVoter.class);
//~ Instance fields ================================================================================================
private AclService aclService;
private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
private String internalMethod;
private String processConfigAttribute;
private Permission[] requirePermission;
//~ Constructors ===================================================================================================
public AclEntryVoter(AclService aclService, String processConfigAttribute, Permission[] requirePermission) {
Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory");
Assert.notNull(aclService, "An AclService is mandatory");
if ((requirePermission == null) || (requirePermission.length == 0)) {
throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
}
this.aclService = aclService;
this.processConfigAttribute = processConfigAttribute;
this.requirePermission = requirePermission;
}
//~ Methods ========================================================================================================
/**
* Optionally specifies a method of the domain object that will be used to obtain a contained domain
* object. That contained domain object will be used for the ACL evaluation. This is useful if a domain object
* contains a parent that an ACL evaluation should be targeted for, instead of the child domain object (which
* perhaps is being created and as such does not yet have any ACL permissions)
*
* @return <code>null</code> to use the domain object, or the name of a method (that requires no arguments) that
* should be invoked to obtain an <code>Object</code> which will be the domain object used for ACL
* evaluation
*/
protected String getInternalMethod() {
return internalMethod;
}
public void setInternalMethod(String internalMethod) {
this.internalMethod = internalMethod;
}
protected String getProcessConfigAttribute() {
return processConfigAttribute;
}
public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required");
this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
}
public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
this.sidRetrievalStrategy = sidRetrievalStrategy;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null) && attribute.getAttribute().equals(getProcessConfigAttribute())) {
return true;
} else {
return false;
}
}
public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
Iterator iter = config.getConfigAttributes().iterator();
while (iter.hasNext()) {
ConfigAttribute attr = (ConfigAttribute) iter.next();
if (!this.supports(attr)) {
continue;
}
// Need to make an access decision on this invocation
// Attempt to locate the domain object instance to process
Object domainObject = getDomainObjectInstance(object);
// If domain object is null, vote to abstain
if (domainObject == null) {
if (logger.isDebugEnabled()) {
logger.debug("Voting to abstain - domainObject is null");
}
return AccessDecisionVoter.ACCESS_ABSTAIN;
}
// Evaluate if we are required to use an inner domain object
if (StringUtils.hasText(internalMethod)) {
try {
Class clazz = domainObject.getClass();
Method method = clazz.getMethod(internalMethod, new Class[0]);
domainObject = method.invoke(domainObject, new Object[0]);
} catch (NoSuchMethodException nsme) {
throw new AuthorizationServiceException("Object of class '" + domainObject.getClass()
+ "' does not provide the requested internalMethod: " + internalMethod);
} catch (IllegalAccessException iae) {
logger.debug("IllegalAccessException", iae);
throw new AuthorizationServiceException("Problem invoking internalMethod: " + internalMethod
+ " for object: " + domainObject);
} catch (InvocationTargetException ite) {
logger.debug("InvocationTargetException", ite);
throw new AuthorizationServiceException("Problem invoking internalMethod: " + internalMethod
+ " for object: " + domainObject);
}
}
// Obtain the OID applicable to the domain object
ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);
// Obtain the SIDs applicable to the principal
Sid[] sids = sidRetrievalStrategy.getSids(authentication);
Acl acl;
try {
// Lookup only ACLs for SIDs we're interested in
acl = aclService.readAclById(objectIdentity, sids);
} catch (NotFoundException nfe) {
if (logger.isDebugEnabled()) {
logger.debug("Voting to deny access - no ACLs apply for this principal");
}
return AccessDecisionVoter.ACCESS_DENIED;
}
try {
if (acl.isGranted(requirePermission, sids, false)) {
if (logger.isDebugEnabled()) {
logger.debug("Voting to grant access");
}
return AccessDecisionVoter.ACCESS_GRANTED;
} else {
if (logger.isDebugEnabled()) {
logger.debug(
"Voting to deny access - ACLs returned, but insufficient permissions for this principal");
}
return AccessDecisionVoter.ACCESS_DENIED;
}
} catch (NotFoundException nfe) {
if (logger.isDebugEnabled()) {
logger.debug("Voting to deny access - no ACLs apply for this principal");
}
return AccessDecisionVoter.ACCESS_DENIED;
}
}
// No configuration attribute matched, so abstain
return AccessDecisionVoter.ACCESS_ABSTAIN;
}
}
@@ -1,154 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>security</short-name>
<uri>http://www.springframework.org/security/tags</uri>
<description>
Spring Security Authorization Tag Library
$Id$
</description>
<tag>
<name>authorize</name>
<tag-class>org.springframework.security.taglibs.authz.AuthorizeTag</tag-class>
<description>
A simple tag to output or not the body of the tag if the principal
has or doesn't have certain authorities.
</description>
<attribute>
<name>ifNotGranted</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>
A comma separated list of roles which the user must not have
for the body to be output.
</description>
</attribute>
<attribute>
<name>ifAllGranted</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>
A comma separated list of roles which the user must all
possess for the body to be output.
</description>
</attribute>
<attribute>
<name>ifAnyGranted</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>
A comma separated list of roles, one of which the user must
possess for the body to be output.
</description>
</attribute>
</tag>
<tag>
<name>authentication</name>
<tag-class>org.springframework.security.taglibs.authz.AuthenticationTag</tag-class>
<description>
Allows access to the current Authentication object.
</description>
<attribute>
<name>property</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<description>
Property of the Authentication object which should be output. Supports nested
properties. For example if the principal object is an instance of UserDetails,
te property "principal.username" will return the username. Alternatively, using
"name" will call getName method on the Authentication object directly.
</description>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<description>
Name of the exported scoped variable for the
exception thrown from a nested action. The type of the
scoped variable is the type of the exception thrown.
</description>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<description>
Scope for var.
</description>
</attribute>
</tag>
<tag>
<name>acl</name>
<tag-class>org.springframework.security.taglibs.authz.AclTag</tag-class>
<description>
Allows inclusion of a tag body if the current Authentication
has one of the specified permissions to the presented
domain object instance. This tag uses the first AclManager
it locates via
WebApplicationContextUtils.getRequiredWebApplicationContext(HttpServletContext).
</description>
<attribute>
<name>hasPermission</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<description>
A comma separated list of integers, each representing a
required bit mask permission from a subclass of
org.springframework.security.acl.basic.AbstractBasicAclEntry.
</description>
</attribute>
<attribute>
<name>domainObject</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<description>
The actual domain object instance for which permissions
are being evaluated.
</description>
</attribute>
</tag>
<tag>
<name>accesscontrollist</name>
<tag-class>org.springframework.security.taglibs.authz.AccessControlListTag</tag-class>
<description>
Allows inclusion of a tag body if the current Authentication
has one of the specified permissions to the presented
domain object instance.
</description>
<attribute>
<name>hasPermission</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<description>
A comma separated list of integers, each representing a
required bit mask permission from a subclass of
org.springframework.security.acl.basic.AbstractBasicAclEntry.
</description>
</attribute>
<attribute>
<name>domainObject</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<description>
The actual domain object instance for which permissions
are being evaluated.
</description>
</attribute>
</tag>
</taglib>
@@ -41,8 +41,6 @@ public class MockAclManager implements AclManager {
this.acls = acls;
}
private MockAclManager() {}
//~ Methods ========================================================================================================
public AclEntry[] getAcls(Object domainInstance, Authentication authentication) {
@@ -39,8 +39,6 @@ public class MockPortResolver implements PortResolver {
this.https = https;
}
private MockPortResolver() {}
//~ Methods ========================================================================================================
public int getServerPort(ServletRequest request) {
@@ -1,124 +0,0 @@
package org.springframework.security.acls;
import junit.framework.Assert;
import junit.framework.TestCase;
/**
* Tests for {@link AclFormattingUtils}.
*
* @author Andrei Stefan
*/
public class AclFormattingUtilsTests extends TestCase {
//~ Methods ========================================================================================================
public final void testDemergePatternsParametersConstraints() throws Exception {
try {
AclFormattingUtils.demergePatterns(null, "SOME STRING");
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
AclFormattingUtils.demergePatterns("SOME STRING", null);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
AclFormattingUtils.demergePatterns("SOME STRING", "LONGER SOME STRING");
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
AclFormattingUtils.demergePatterns("SOME STRING", "SAME LENGTH");
Assert.assertTrue(true);
}
catch (IllegalArgumentException notExpected) {
Assert.fail("It shouldn't have thrown IllegalArgumentException");
}
}
public final void testDemergePatterns() throws Exception {
String original = "...........................A...R";
String removeBits = "...............................R";
Assert.assertEquals("...........................A....", AclFormattingUtils
.demergePatterns(original, removeBits));
Assert.assertEquals("ABCDEF", AclFormattingUtils.demergePatterns("ABCDEF", "......"));
Assert.assertEquals("......", AclFormattingUtils.demergePatterns("ABCDEF", "GHIJKL"));
}
public final void testMergePatternsParametersConstraints() throws Exception {
try {
AclFormattingUtils.mergePatterns(null, "SOME STRING");
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
AclFormattingUtils.mergePatterns("SOME STRING", null);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
AclFormattingUtils.mergePatterns("SOME STRING", "LONGER SOME STRING");
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
AclFormattingUtils.mergePatterns("SOME STRING", "SAME LENGTH");
Assert.assertTrue(true);
}
catch (IllegalArgumentException notExpected) {
Assert.fail("It shouldn't have thrown IllegalArgumentException");
}
}
public final void testMergePatterns() throws Exception {
String original = "...............................R";
String extraBits = "...........................A....";
Assert.assertEquals("...........................A...R", AclFormattingUtils
.mergePatterns(original, extraBits));
Assert.assertEquals("ABCDEF", AclFormattingUtils.mergePatterns("ABCDEF", "......"));
Assert.assertEquals("GHIJKL", AclFormattingUtils.mergePatterns("ABCDEF", "GHIJKL"));
}
public final void testBinaryPrints() throws Exception {
Assert.assertEquals("............................****", AclFormattingUtils.printBinary(15));
try {
AclFormattingUtils.printBinary(15, Permission.RESERVED_ON);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException notExpected) {
Assert.assertTrue(true);
}
try {
AclFormattingUtils.printBinary(15, Permission.RESERVED_OFF);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException notExpected) {
Assert.assertTrue(true);
}
Assert.assertEquals("............................xxxx", AclFormattingUtils.printBinary(15, 'x'));
}
}
@@ -1,138 +0,0 @@
package org.springframework.security.acls.domain;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AuditableAccessControlEntry;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.UnloadedSidException;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.Sid;
/**
* Test class for {@link AccessControlEntryImpl}
*
* @author Andrei Stefan
*/
public class AccessControlEntryTests extends TestCase {
//~ Methods ========================================================================================================
public void testConstructorRequiredFields() throws Exception {
// Check Acl field is present
try {
AccessControlEntry ace = new AccessControlEntryImpl(null, null, new PrincipalSid("johndoe"),
BasePermission.ADMINISTRATION, true, true, true);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
// Check Sid field is present
try {
AccessControlEntry ace = new AccessControlEntryImpl(null, new MockAcl(), null,
BasePermission.ADMINISTRATION, true, true, true);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
// Check Permission field is present
try {
AccessControlEntry ace = new AccessControlEntryImpl(null, new MockAcl(), new PrincipalSid("johndoe"), null,
true, true, true);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
}
public void testAccessControlEntryImplGetters() throws Exception {
Acl mockAcl = new MockAcl();
Sid sid = new PrincipalSid("johndoe");
// Create a sample entry
AccessControlEntry ace = new AccessControlEntryImpl(new Long(1), mockAcl, sid, BasePermission.ADMINISTRATION,
true, true, true);
// and check every get() method
Assert.assertEquals(new Long(1), ace.getId());
Assert.assertEquals(mockAcl, ace.getAcl());
Assert.assertEquals(sid, ace.getSid());
Assert.assertTrue(ace.isGranting());
Assert.assertEquals(BasePermission.ADMINISTRATION, ace.getPermission());
Assert.assertTrue(((AuditableAccessControlEntry) ace).isAuditFailure());
Assert.assertTrue(((AuditableAccessControlEntry) ace).isAuditSuccess());
}
public void testEquals() throws Exception {
Acl mockAcl = new MockAcl();
Sid sid = new PrincipalSid("johndoe");
AccessControlEntry ace = new AccessControlEntryImpl(new Long(1), mockAcl, sid, BasePermission.ADMINISTRATION,
true, true, true);
Assert.assertFalse(ace.equals(null));
Assert.assertFalse(ace.equals(new Long(100)));
Assert.assertTrue(ace.equals(ace));
Assert.assertTrue(ace.equals(new AccessControlEntryImpl(new Long(1), mockAcl, sid,
BasePermission.ADMINISTRATION, true, true, true)));
Assert.assertFalse(ace.equals(new AccessControlEntryImpl(new Long(2), mockAcl, sid,
BasePermission.ADMINISTRATION, true, true, true)));
Assert.assertFalse(ace.equals(new AccessControlEntryImpl(new Long(1), new MockAcl(), sid,
BasePermission.ADMINISTRATION, true, true, true)));
Assert.assertFalse(ace.equals(new AccessControlEntryImpl(new Long(1), mockAcl, new PrincipalSid("scott"),
BasePermission.ADMINISTRATION, true, true, true)));
Assert.assertFalse(ace.equals(new AccessControlEntryImpl(new Long(1), mockAcl, sid, BasePermission.WRITE, true,
true, true)));
Assert.assertFalse(ace.equals(new AccessControlEntryImpl(new Long(1), mockAcl, sid,
BasePermission.ADMINISTRATION, false, true, true)));
Assert.assertFalse(ace.equals(new AccessControlEntryImpl(new Long(1), mockAcl, sid,
BasePermission.ADMINISTRATION, true, false, true)));
Assert.assertFalse(ace.equals(new AccessControlEntryImpl(new Long(1), mockAcl, sid,
BasePermission.ADMINISTRATION, true, true, false)));
}
//~ Inner Classes ==================================================================================================
private class MockAcl implements Acl {
public AccessControlEntry[] getEntries() {
return null;
}
public ObjectIdentity getObjectIdentity() {
return null;
}
public Sid getOwner() {
return null;
}
public Acl getParentAcl() {
return null;
}
public boolean isEntriesInheriting() {
return false;
}
public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
throws NotFoundException, UnloadedSidException {
return false;
}
public boolean isSidLoaded(Sid[] sids) {
return false;
}
}
}
@@ -1,638 +0,0 @@
package org.springframework.security.acls.domain;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AlreadyExistsException;
import org.springframework.security.acls.AuditableAccessControlEntry;
import org.springframework.security.acls.AuditableAcl;
import org.springframework.security.acls.ChildrenExistException;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.MutableAclService;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.OwnershipAcl;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.acls.sid.GrantedAuthoritySid;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.TestingAuthenticationToken;
import org.springframework.security.util.FieldUtils;
/**
* Tests for {@link AclImpl}.
*
* @author Andrei Stefan
*/
public class AclImplTests extends TestCase {
// ~ Methods ========================================================================================================
@Override
protected void setUp() throws Exception {
super.setUp();
}
@Override
protected void tearDown() throws Exception {
SecurityContextHolder.clearContext();
super.tearDown();
}
public void testConstructorsRejectNullParameters() throws Exception {
Authentication auth = new TestingAuthenticationToken("johndoe", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMINISTRATOR") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
try {
Acl acl = new AclImpl(null, new Long(1), strategy, auditLogger);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(identity, null, strategy, auditLogger);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(identity, new Long(1), null, auditLogger);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(identity, new Long(1), strategy, null);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(null, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid("johndoe"));
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(identity, null, strategy, auditLogger, null, null, true, new PrincipalSid("johndoe"));
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(identity, new Long(1), null, auditLogger, null, null, true, new PrincipalSid("johndoe"));
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(identity, new Long(1), strategy, null, null, null, true, new PrincipalSid("johndoe"));
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
Acl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, null);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testInsertAceRejectsNullParameters() throws Exception {
Authentication auth = new TestingAuthenticationToken("johndoe", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMINISTRATOR") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
try {
acl.insertAce(new Long(1), null, new GrantedAuthoritySid("ROLE_IGNORED"), true);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
acl.insertAce(new Long(1), BasePermission.READ, null, true);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testInsertAceAddsElementAtCorrectIndex() throws Exception {
Authentication auth = new TestingAuthenticationToken("johndoe", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMINISTRATOR") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
MockAclService service = new MockAclService();
// Insert one permission
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
service.updateAcl(acl);
// Check it was successfully added
assertEquals(1, acl.getEntries().length);
assertEquals(acl.getEntries()[0].getAcl(), acl);
assertEquals(acl.getEntries()[0].getPermission(), BasePermission.READ);
assertEquals(acl.getEntries()[0].getSid(), new GrantedAuthoritySid("ROLE_TEST1"));
// Add a second permission
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
service.updateAcl(acl);
// Check it was added on the last position
assertEquals(2, acl.getEntries().length);
assertEquals(acl.getEntries()[1].getAcl(), acl);
assertEquals(acl.getEntries()[1].getPermission(), BasePermission.READ);
assertEquals(acl.getEntries()[1].getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
// Add a third permission, after the first one
acl.insertAce(acl.getEntries()[0].getId(), BasePermission.WRITE, new GrantedAuthoritySid("ROLE_TEST3"), false);
service.updateAcl(acl);
assertEquals(3, acl.getEntries().length);
// Check the third entry was added between the two existent ones
assertEquals(acl.getEntries()[0].getPermission(), BasePermission.READ);
assertEquals(acl.getEntries()[0].getSid(), new GrantedAuthoritySid("ROLE_TEST1"));
assertEquals(acl.getEntries()[1].getPermission(), BasePermission.WRITE);
assertEquals(acl.getEntries()[1].getSid(), new GrantedAuthoritySid("ROLE_TEST3"));
assertEquals(acl.getEntries()[2].getPermission(), BasePermission.READ);
assertEquals(acl.getEntries()[2].getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
}
public void testInsertAceFailsForInexistentElement() throws Exception {
Authentication auth = new TestingAuthenticationToken("johndoe", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMINISTRATOR") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
MockAclService service = new MockAclService();
// Insert one permission
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
service.updateAcl(acl);
try {
acl.insertAce(new Long(5), BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
}
public void testDeleteAceKeepsInitialOrdering() throws Exception {
Authentication auth = new TestingAuthenticationToken("johndoe", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMINISTRATOR") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
MockAclService service = new MockAclService();
// Add several permissions
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST3"), true);
service.updateAcl(acl);
// Delete first permission and check the order of the remaining permissions is kept
acl.deleteAce(new Long(1));
assertEquals(2, acl.getEntries().length);
assertEquals(acl.getEntries()[0].getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
assertEquals(acl.getEntries()[1].getSid(), new GrantedAuthoritySid("ROLE_TEST3"));
// Add one more permission and remove the permission in the middle
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST4"), true);
service.updateAcl(acl);
acl.deleteAce(new Long(2));
assertEquals(2, acl.getEntries().length);
assertEquals(acl.getEntries()[0].getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
assertEquals(acl.getEntries()[1].getSid(), new GrantedAuthoritySid("ROLE_TEST4"));
// Remove remaining permissions
acl.deleteAce(new Long(1));
acl.deleteAce(new Long(3));
assertEquals(0, acl.getEntries().length);
}
public void testDeleteAceFailsForInexistentElement() throws Exception {
Authentication auth = new TestingAuthenticationToken("johndoe", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMINISTRATOR") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
try {
acl.deleteAce(new Long(1));
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
}
public void testIsGrantingRejectsEmptyParameters() throws Exception {
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
try {
acl.isGranted(new Permission[] {}, new Sid[] { new PrincipalSid("ben") }, false);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
acl.isGranted(new Permission[] { BasePermission.READ }, new Sid[] {}, false);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testIsGrantingGrantsAccessForAclWithNoParent() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored", new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_GENERAL"), new GrantedAuthorityImpl("ROLE_GUEST") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity rootOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
// Create an ACL which owner is not the authenticated principal
MutableAcl rootAcl = new AclImpl(rootOid, new Long(1), strategy, auditLogger, null, null, false, new PrincipalSid(
"johndoe"));
// Grant some permissions
rootAcl.insertAce(null, BasePermission.READ, new PrincipalSid("ben"), false);
rootAcl.insertAce(null, BasePermission.WRITE, new PrincipalSid("scott"), true);
rootAcl.insertAce(null, BasePermission.WRITE, new PrincipalSid("rod"), false);
rootAcl.insertAce(null, BasePermission.WRITE, new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), true);
// Check permissions granting
Permission[] permissions = new Permission[] { BasePermission.READ, BasePermission.CREATE };
Sid[] sids = new Sid[] { new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_GUEST") };
assertFalse(rootAcl.isGranted(permissions, sids, false));
try {
rootAcl.isGranted(permissions, new Sid[] { new PrincipalSid("scott") }, false);
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
assertTrue(rootAcl.isGranted(new Permission[] { BasePermission.WRITE }, new Sid[] { new PrincipalSid("scott") },
false));
assertFalse(rootAcl.isGranted(new Permission[] { BasePermission.WRITE }, new Sid[] { new PrincipalSid("rod"),
new GrantedAuthoritySid("WRITE_ACCESS_ROLE") }, false));
assertTrue(rootAcl.isGranted(new Permission[] { BasePermission.WRITE }, new Sid[] {
new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), new PrincipalSid("rod") }, false));
try {
// Change the type of the Sid and check the granting process
rootAcl.isGranted(new Permission[] { BasePermission.WRITE }, new Sid[] { new GrantedAuthoritySid("rod"),
new PrincipalSid("WRITE_ACCESS_ROLE") }, false);
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
}
public void testIsGrantingGrantsAccessForInheritableAcls() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_GENERAL") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity grandParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity parentOid1 = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity parentOid2 = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
ObjectIdentity childOid1 = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(103));
ObjectIdentity childOid2 = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(104));
// Create ACLs
MutableAcl grandParentAcl = new AclImpl(grandParentOid, new Long(1), strategy, auditLogger, null, null, false,
new PrincipalSid("johndoe"));
MutableAcl parentAcl1 = new AclImpl(parentOid1, new Long(2), strategy, auditLogger, null, null, true,
new PrincipalSid("johndoe"));
MutableAcl parentAcl2 = new AclImpl(parentOid2, new Long(3), strategy, auditLogger, null, null, true,
new PrincipalSid("johndoe"));
MutableAcl childAcl1 = new AclImpl(childOid1, new Long(4), strategy, auditLogger, null, null, true,
new PrincipalSid("johndoe"));
MutableAcl childAcl2 = new AclImpl(childOid2, new Long(4), strategy, auditLogger, null, null, false,
new PrincipalSid("johndoe"));
// Create hierarchies
childAcl2.setParent(childAcl1);
childAcl1.setParent(parentAcl1);
parentAcl2.setParent(grandParentAcl);
parentAcl1.setParent(grandParentAcl);
// Add some permissions
grandParentAcl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
grandParentAcl.insertAce(null, BasePermission.WRITE, new PrincipalSid("ben"), true);
grandParentAcl.insertAce(null, BasePermission.DELETE, new PrincipalSid("ben"), false);
grandParentAcl.insertAce(null, BasePermission.DELETE, new PrincipalSid("scott"), true);
parentAcl1.insertAce(null, BasePermission.READ, new PrincipalSid("scott"), true);
parentAcl1.insertAce(null, BasePermission.DELETE, new PrincipalSid("scott"), false);
parentAcl2.insertAce(null, BasePermission.CREATE, new PrincipalSid("ben"), true);
childAcl1.insertAce(null, BasePermission.CREATE, new PrincipalSid("scott"), true);
// Check granting process for parent1
assertTrue(parentAcl1.isGranted(new Permission[] { BasePermission.READ }, new Sid[] { new PrincipalSid("scott") },
false));
assertTrue(parentAcl1.isGranted(new Permission[] { BasePermission.READ }, new Sid[] { new GrantedAuthoritySid(
"ROLE_USER_READ") }, false));
assertTrue(parentAcl1.isGranted(new Permission[] { BasePermission.WRITE }, new Sid[] { new PrincipalSid("ben") },
false));
assertFalse(parentAcl1.isGranted(new Permission[] { BasePermission.DELETE }, new Sid[] { new PrincipalSid("ben") },
false));
assertFalse(parentAcl1.isGranted(new Permission[] { BasePermission.DELETE },
new Sid[] { new PrincipalSid("scott") }, false));
// Check granting process for parent2
assertTrue(parentAcl2.isGranted(new Permission[] { BasePermission.CREATE }, new Sid[] { new PrincipalSid("ben") },
false));
assertTrue(parentAcl2.isGranted(new Permission[] { BasePermission.WRITE }, new Sid[] { new PrincipalSid("ben") },
false));
assertFalse(parentAcl2.isGranted(new Permission[] { BasePermission.DELETE }, new Sid[] { new PrincipalSid("ben") },
false));
// Check granting process for child1
assertTrue(childAcl1.isGranted(new Permission[] { BasePermission.CREATE }, new Sid[] { new PrincipalSid("scott") },
false));
assertTrue(childAcl1.isGranted(new Permission[] { BasePermission.READ }, new Sid[] { new GrantedAuthoritySid(
"ROLE_USER_READ") }, false));
assertFalse(childAcl1.isGranted(new Permission[] { BasePermission.DELETE }, new Sid[] { new PrincipalSid("ben") },
false));
// Check granting process for child2 (doesn't inherit the permissions from its parent)
try {
assertTrue(childAcl2.isGranted(new Permission[] { BasePermission.CREATE },
new Sid[] { new PrincipalSid("scott") }, false));
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
try {
assertTrue(childAcl2.isGranted(new Permission[] { BasePermission.CREATE }, new Sid[] { new PrincipalSid(
"johndoe") }, false));
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
}
public void testUpdateAce() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_GENERAL") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, false, new PrincipalSid(
"johndoe"));
MockAclService service = new MockAclService();
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
acl.insertAce(null, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true);
acl.insertAce(null, BasePermission.CREATE, new PrincipalSid("ben"), true);
service.updateAcl(acl);
assertEquals(acl.getEntries()[0].getPermission(), BasePermission.READ);
assertEquals(acl.getEntries()[1].getPermission(), BasePermission.WRITE);
assertEquals(acl.getEntries()[2].getPermission(), BasePermission.CREATE);
// Change each permission
acl.updateAce(new Long(1), BasePermission.CREATE);
acl.updateAce(new Long(2), BasePermission.DELETE);
acl.updateAce(new Long(3), BasePermission.READ);
// Check the change was successfuly made
assertEquals(acl.getEntries()[0].getPermission(), BasePermission.CREATE);
assertEquals(acl.getEntries()[1].getPermission(), BasePermission.DELETE);
assertEquals(acl.getEntries()[2].getPermission(), BasePermission.READ);
}
public void testUpdateAuditing() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored", new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_AUDITING"), new GrantedAuthorityImpl("ROLE_GENERAL") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, false, new PrincipalSid(
"johndoe"));
MockAclService service = new MockAclService();
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
acl.insertAce(null, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true);
service.updateAcl(acl);
assertFalse(((AuditableAccessControlEntry) acl.getEntries()[0]).isAuditFailure());
assertFalse(((AuditableAccessControlEntry) acl.getEntries()[1]).isAuditFailure());
assertFalse(((AuditableAccessControlEntry) acl.getEntries()[0]).isAuditSuccess());
assertFalse(((AuditableAccessControlEntry) acl.getEntries()[1]).isAuditSuccess());
// Change each permission
((AuditableAcl) acl).updateAuditing(new Long(1), true, true);
((AuditableAcl) acl).updateAuditing(new Long(2), true, true);
// Check the change was successfuly made
assertTrue(((AuditableAccessControlEntry) acl.getEntries()[0]).isAuditFailure());
assertTrue(((AuditableAccessControlEntry) acl.getEntries()[1]).isAuditFailure());
assertTrue(((AuditableAccessControlEntry) acl.getEntries()[0]).isAuditSuccess());
assertTrue(((AuditableAccessControlEntry) acl.getEntries()[1]).isAuditSuccess());
}
public void testGettersSetters() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored", new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_GENERAL") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_GENERAL"), new GrantedAuthorityImpl("ROLE_GENERAL"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity identity2 = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
MutableAcl parentAcl = new AclImpl(identity2, new Long(2), strategy, auditLogger, null, null, true, new PrincipalSid(
"johndoe"));
MockAclService service = new MockAclService();
acl.insertAce(null, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
acl.insertAce(null, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true);
service.updateAcl(acl);
assertEquals(acl.getId(), new Long(1));
assertEquals(acl.getObjectIdentity(), identity);
assertEquals(acl.getOwner(), new PrincipalSid("johndoe"));
assertNull(acl.getParentAcl());
assertTrue(acl.isEntriesInheriting());
assertEquals(2, acl.getEntries().length);
acl.setParent(parentAcl);
assertEquals(acl.getParentAcl(), parentAcl);
acl.setEntriesInheriting(false);
assertFalse(acl.isEntriesInheriting());
((OwnershipAcl) acl).setOwner(new PrincipalSid("ben"));
assertEquals(acl.getOwner(), new PrincipalSid("ben"));
}
public void testIsSidLoaded() throws Exception {
AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_GENERAL"), new GrantedAuthorityImpl("ROLE_GENERAL"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
AuditLogger auditLogger = new ConsoleAuditLogger();
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
Sid[] loadedSids = new Sid[] { new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_IGNORED") };
MutableAcl acl = new AclImpl(identity, new Long(1), strategy, auditLogger, null, loadedSids, true, new PrincipalSid(
"johndoe"));
assertTrue(acl.isSidLoaded(loadedSids));
assertTrue(acl.isSidLoaded(new Sid[] { new GrantedAuthoritySid("ROLE_IGNORED"), new PrincipalSid("ben") }));
assertTrue(acl.isSidLoaded(new Sid[] { new GrantedAuthoritySid("ROLE_IGNORED")}));
assertTrue(acl.isSidLoaded(new Sid[] { new PrincipalSid("ben") }));
assertTrue(acl.isSidLoaded(null));
assertTrue(acl.isSidLoaded(new Sid[] { }));
assertTrue(acl.isSidLoaded(new Sid[] { new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_IGNORED") }));
assertFalse(acl.isSidLoaded(new Sid[] { new GrantedAuthoritySid("ROLE_GENERAL"), new GrantedAuthoritySid("ROLE_IGNORED") }));
assertFalse(acl.isSidLoaded(new Sid[] { new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_GENERAL") }));
}
// ~ Inner Classes ==================================================================================================
private class MockAclService implements MutableAclService {
public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException {
return null;
}
public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException {
}
/*
* Mock implementation that populates the aces list with fully initialized AccessControlEntries
* @see org.springframework.security.acls.MutableAclService#updateAcl(org.springframework.security.acls.MutableAcl)
*/
public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
AccessControlEntry[] oldAces = acl.getEntries();
Field acesField = FieldUtils.getField(AclImpl.class, "aces");
acesField.setAccessible(true);
List newAces;
try {
newAces = (List) acesField.get(acl);
newAces.clear();
for (int i = 0; i < oldAces.length; i++) {
AccessControlEntry ac = oldAces[i];
// Just give an ID to all this acl's aces, rest of the fields are just copied
newAces.add(new AccessControlEntryImpl(new Long(i + 1), ac.getAcl(), ac.getSid(), ac.getPermission(), ac
.isGranting(), ((AuditableAccessControlEntry) ac).isAuditSuccess(),
((AuditableAccessControlEntry) ac).isAuditFailure()));
}
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
return acl;
}
public ObjectIdentity[] findChildren(ObjectIdentity parentIdentity) {
return null;
}
public Acl readAclById(ObjectIdentity object) throws NotFoundException {
return null;
}
public Acl readAclById(ObjectIdentity object, Sid[] sids) throws NotFoundException {
return null;
}
public Map readAclsById(ObjectIdentity[] objects) throws NotFoundException {
return null;
}
public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException {
return null;
}
}
}
@@ -1,296 +0,0 @@
package org.springframework.security.acls.domain;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.TestingAuthenticationToken;
/**
* Test class for {@link AclAuthorizationStrategyImpl} and {@link AclImpl}
* security checks.
*
* @author Andrei Stefan
*/
public class AclImplementationSecurityCheckTests extends TestCase {
//~ Methods ========================================================================================================
protected void setUp() throws Exception {
SecurityContextHolder.clearContext();
}
protected void tearDown() throws Exception {
SecurityContextHolder.clearContext();
}
public void testSecurityCheckNoACEs() throws Exception {
Authentication auth = new TestingAuthenticationToken("user", "password", new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_GENERAL"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_OWNERSHIP") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
Acl acl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
try {
aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
try {
aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
try {
aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
// Create another authorization strategy
AclAuthorizationStrategy aclAuthorizationStrategy2 = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO"),
new GrantedAuthorityImpl("ROLE_THREE") });
Acl acl2 = new AclImpl(identity, new Long(1), aclAuthorizationStrategy2, new ConsoleAuditLogger());
// Check access in case the principal has no authorization rights
try {
aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
Assert.assertTrue(true);
}
try {
aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
Assert.assertTrue(true);
}
try {
aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
Assert.assertTrue(true);
}
}
public void testSecurityCheckWithMultipleACEs() throws Exception {
// Create a simple authentication with ROLE_GENERAL
Authentication auth = new TestingAuthenticationToken("user", "password",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_GENERAL") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
// Authorization strategy will require a different role for each access
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
// Let's give the principal the ADMINISTRATION permission, without
// granting access
MutableAcl aclFirstDeny = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
aclFirstDeny.insertAce(null, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false);
// The CHANGE_GENERAL test should pass as the principal has ROLE_GENERAL
try {
aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
// The CHANGE_AUDITING and CHANGE_OWNERSHIP should fail since the
// principal doesn't have these authorities,
// nor granting access
try {
aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.fail("It should have thrown AccessDeniedException");
}
catch (AccessDeniedException expected) {
Assert.assertTrue(true);
}
try {
aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.fail("It should have thrown AccessDeniedException");
}
catch (AccessDeniedException expected) {
Assert.assertTrue(true);
}
// Add granting access to this principal
aclFirstDeny.insertAce(null, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
// and try again for CHANGE_AUDITING - the first ACE's granting flag
// (false) will deny this access
try {
aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.fail("It should have thrown AccessDeniedException");
}
catch (AccessDeniedException expected) {
Assert.assertTrue(true);
}
// Create another ACL and give the principal the ADMINISTRATION
// permission, with granting access
MutableAcl aclFirstAllow = new AclImpl(identity, new Long(1), aclAuthorizationStrategy,
new ConsoleAuditLogger());
aclFirstAllow.insertAce(null, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
// The CHANGE_AUDITING test should pass as there is one ACE with
// granting access
try {
aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
// Add a deny ACE and test again for CHANGE_AUDITING
aclFirstAllow.insertAce(null, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false);
try {
aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
// Create an ACL with no ACE
MutableAcl aclNoACE = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
try {
aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
Assert.assertTrue(true);
}
// and still grant access for CHANGE_GENERAL
try {
aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.assertTrue(true);
}
catch (NotFoundException expected) {
Assert.fail("It shouldn't have thrown NotFoundException");
}
}
public void testSecurityCheckWithInheritableACEs() throws Exception {
// Create a simple authentication with ROLE_GENERAL
Authentication auth = new TestingAuthenticationToken("user", "password",
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_GENERAL") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
// Authorization strategy will require a different role for each access
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
// Let's give the principal an ADMINISTRATION permission, with granting
// access
MutableAcl parentAcl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
parentAcl.insertAce(null, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
MutableAcl childAcl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
// Check against the 'child' acl, which doesn't offer any authorization
// rights on CHANGE_OWNERSHIP
try {
aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
Assert.assertTrue(true);
}
// Link the child with its parent and test again against the
// CHANGE_OWNERSHIP right
childAcl.setParent(parentAcl);
childAcl.setEntriesInheriting(true);
try {
aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.assertTrue(true);
}
catch (NotFoundException expected) {
Assert.fail("It shouldn't have thrown NotFoundException");
}
// Create a root parent and link it to the middle parent
MutableAcl rootParentAcl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy,
new ConsoleAuditLogger());
parentAcl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
rootParentAcl.insertAce(null, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
parentAcl.setEntriesInheriting(true);
parentAcl.setParent(rootParentAcl);
childAcl.setParent(parentAcl);
try {
aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.assertTrue(true);
}
catch (NotFoundException expected) {
Assert.fail("It shouldn't have thrown NotFoundException");
}
}
public void testSecurityCheckPrincipalOwner() throws Exception {
Authentication auth = new TestingAuthenticationToken("user", "password", new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_ONE"),
new GrantedAuthorityImpl("ROLE_ONE") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
Acl acl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger(), null, null,
false, new PrincipalSid(auth));
try {
aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
try {
aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING);
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
catch (NotFoundException expected) {
Assert.assertTrue(true);
}
try {
aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
Assert.assertTrue(true);
}
catch (AccessDeniedException notExpected) {
Assert.fail("It shouldn't have thrown AccessDeniedException");
}
}
}
@@ -1,137 +0,0 @@
package org.springframework.security.acls.domain;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AuditableAccessControlEntry;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.sid.Sid;
/**
* Test class for {@link ConsoleAuditLogger}.
*
* @author Andrei Stefan
*/
public class AuditLoggerTests extends TestCase {
//~ Instance fields ================================================================================================
private PrintStream console;
private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
//~ Methods ========================================================================================================
public void setUp() throws Exception {
console = System.out;
System.setOut(new PrintStream(bytes));
}
public void tearDown() throws Exception {
System.setOut(console);
}
public void testLoggingTests() throws Exception {
ConsoleAuditLogger logger = new ConsoleAuditLogger();
MockAccessControlEntryImpl auditableAccessControlEntry = new MockAccessControlEntryImpl();
logger.logIfNeeded(true, auditableAccessControlEntry);
Assert.assertTrue(bytes.size() == 0);
bytes.reset();
logger.logIfNeeded(false, auditableAccessControlEntry);
Assert.assertTrue(bytes.size() == 0);
auditableAccessControlEntry.setAuditSuccess(true);
bytes.reset();
logger.logIfNeeded(true, auditableAccessControlEntry);
Assert.assertTrue(bytes.toString().length() > 0);
Assert.assertTrue(bytes.toString().startsWith("GRANTED due to ACE"));
auditableAccessControlEntry.setAuditFailure(true);
bytes.reset();
logger.logIfNeeded(false, auditableAccessControlEntry);
Assert.assertTrue(bytes.toString().length() > 0);
Assert.assertTrue(bytes.toString().startsWith("DENIED due to ACE"));
MockAccessControlEntry accessControlEntry = new MockAccessControlEntry();
bytes.reset();
logger.logIfNeeded(true, accessControlEntry);
Assert.assertTrue(bytes.size() == 0);
}
//~ Inner Classes ==================================================================================================
private class MockAccessControlEntryImpl implements AuditableAccessControlEntry {
private boolean auditFailure = false;
private boolean auditSuccess = false;
public boolean isAuditFailure() {
return auditFailure;
}
public boolean isAuditSuccess() {
return auditSuccess;
}
public Acl getAcl() {
return null;
}
public Serializable getId() {
return null;
}
public Permission getPermission() {
return null;
}
public Sid getSid() {
return null;
}
public boolean isGranting() {
return false;
}
public void setAuditFailure(boolean auditFailure) {
this.auditFailure = auditFailure;
}
public void setAuditSuccess(boolean auditSuccess) {
this.auditSuccess = auditSuccess;
}
}
private class MockAccessControlEntry implements AccessControlEntry {
public Acl getAcl() {
return null;
}
public Serializable getId() {
return null;
}
public Permission getPermission() {
return null;
}
public Sid getSid() {
return null;
}
public boolean isGranting() {
return false;
}
}
}
@@ -1,101 +0,0 @@
/* 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.springframework.security.acls.domain;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.security.acls.Permission;
/**
* Tests BasePermission and CumulativePermission.
*
* @author Ben Alex
* @version $Id${date}
*/
public class PermissionTests {
private static final Log LOGGER = LogFactory.getLog(PermissionTests.class);
//~ Methods ========================================================================================================
@Test
public void expectedIntegerValues() {
assertEquals(1, BasePermission.READ.getMask());
assertEquals(16, BasePermission.ADMINISTRATION.getMask());
assertEquals(7,
new CumulativePermission().set(BasePermission.READ).set(BasePermission.WRITE).set(BasePermission.CREATE)
.getMask());
assertEquals(17,
new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask());
}
@Test
public void fromInteger() {
Permission permission = BasePermission.buildFromMask(7);
System.out.println("7 = " + permission.toString());
permission = BasePermission.buildFromMask(4);
System.out.println("4 = " + permission.toString());
}
@Test
public void stringConversion() {
System.out.println("R = " + BasePermission.READ.toString());
assertEquals("BasePermission[...............................R=1]", BasePermission.READ.toString());
System.out.println("A = " + BasePermission.ADMINISTRATION.toString());
assertEquals("BasePermission[...........................A....=16]", BasePermission.ADMINISTRATION.toString());
System.out.println("R = " + new CumulativePermission().set(BasePermission.READ).toString());
assertEquals("CumulativePermission[...............................R=1]",
new CumulativePermission().set(BasePermission.READ).toString());
System.out.println("A = " + new CumulativePermission().set(BasePermission.ADMINISTRATION).toString());
assertEquals("CumulativePermission[...........................A....=16]",
new CumulativePermission().set(BasePermission.ADMINISTRATION).toString());
System.out.println("RA = "
+ new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
assertEquals("CumulativePermission[...........................A...R=17]",
new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
System.out.println("R = "
+ new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
.clear(BasePermission.ADMINISTRATION).toString());
assertEquals("CumulativePermission[...............................R=1]",
new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
.clear(BasePermission.ADMINISTRATION).toString());
System.out.println("0 = "
+ new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
.clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
assertEquals("CumulativePermission[................................=0]",
new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
.clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
}
}
@@ -1,294 +0,0 @@
package org.springframework.security.acls.jdbc;
import java.util.Map;
import junit.framework.Assert;
import net.sf.ehcache.Ehcache;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.MockApplicationContext;
import org.springframework.security.TestDataSource;
import org.springframework.security.acls.Acl;
import org.springframework.security.acls.AuditableAccessControlEntry;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.Sid;
import org.springframework.util.FileCopyUtils;
/**
* Tests {@link BasicLookupStrategy}
*
* @author Andrei Stefan
*/
public class BasicLookupStrategyTests {
//~ Instance fields ================================================================================================
private static JdbcTemplate jdbcTemplate;
private LookupStrategy strategy;
private static TestDataSource dataSource;
// ~ Methods ========================================================================================================
@BeforeClass
public static void createDatabase() throws Exception {
dataSource = new TestDataSource("lookupstrategytest");
jdbcTemplate = new JdbcTemplate(dataSource);
Resource resource = new ClassPathResource("org/springframework/security/acls/jdbc/testData.sql");
String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
jdbcTemplate.execute(sql);
}
@AfterClass
public static void dropDatabase() throws Exception {
dataSource.destroy();
}
@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,'org.springframework.security.TargetObject');"
+ "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());
AclAuthorizationStrategy authorizationStrategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_ADMINISTRATOR"), new GrantedAuthorityImpl("ROLE_ADMINISTRATOR"),
new GrantedAuthorityImpl("ROLE_ADMINISTRATOR") });
strategy = new BasicLookupStrategy(dataSource, cache, authorizationStrategy, new ConsoleAuditLogger());
}
@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);
}
private Ehcache getCache() {
ApplicationContext ctx = MockApplicationContext.getContext();
return (Ehcache) ctx.getBean("eHCacheBackend");
}
@Test
public void testAclsRetrievalWithDefaultBatchSize() throws Exception {
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity middleParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
Map map = this.strategy.readAclsById(new ObjectIdentity[] { topParentOid, middleParentOid, childOid }, null);
checkEntries(topParentOid, middleParentOid, childOid, map);
}
@Test
public void testAclsRetrievalFromCacheOnly() throws Exception {
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity middleParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
// Objects were put in cache
strategy.readAclsById(new ObjectIdentity[] { topParentOid, middleParentOid, childOid }, null);
// Let's empty the database to force acls retrieval from cache
emptyDatabase();
Map map = this.strategy.readAclsById(new ObjectIdentity[] { topParentOid, middleParentOid, childOid }, null);
checkEntries(topParentOid, middleParentOid, childOid, map);
}
@Test
public void testAclsRetrievalWithCustomBatchSize() throws Exception {
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity middleParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
// Set a batch size to allow multiple database queries in order to retrieve all acls
((BasicLookupStrategy) this.strategy).setBatchSize(1);
Map map = this.strategy.readAclsById(new ObjectIdentity[] { topParentOid, middleParentOid, childOid }, null);
checkEntries(topParentOid, middleParentOid, childOid, map);
}
private void checkEntries(ObjectIdentity topParentOid, ObjectIdentity middleParentOid, ObjectIdentity childOid, Map map)
throws Exception {
Assert.assertEquals(3, map.size());
MutableAcl topParent = (MutableAcl) map.get(topParentOid);
MutableAcl middleParent = (MutableAcl) map.get(middleParentOid);
MutableAcl child = (MutableAcl) map.get(childOid);
// Check the retrieved versions has IDs
Assert.assertNotNull(topParent.getId());
Assert.assertNotNull(middleParent.getId());
Assert.assertNotNull(child.getId());
// Check their parents were correctly retrieved
Assert.assertNull(topParent.getParentAcl());
Assert.assertEquals(topParentOid, middleParent.getParentAcl().getObjectIdentity());
Assert.assertEquals(middleParentOid, child.getParentAcl().getObjectIdentity());
// Check their ACEs were correctly retrieved
Assert.assertEquals(2, topParent.getEntries().length);
Assert.assertEquals(1, middleParent.getEntries().length);
Assert.assertEquals(1, child.getEntries().length);
// Check object identities were correctly retrieved
Assert.assertEquals(topParentOid, topParent.getObjectIdentity());
Assert.assertEquals(middleParentOid, middleParent.getObjectIdentity());
Assert.assertEquals(childOid, child.getObjectIdentity());
// Check each entry
Assert.assertTrue(topParent.isEntriesInheriting());
Assert.assertEquals(topParent.getId(), new Long(1));
Assert.assertEquals(topParent.getOwner(), new PrincipalSid("ben"));
Assert.assertEquals(topParent.getEntries()[0].getId(), new Long(1));
Assert.assertEquals(topParent.getEntries()[0].getPermission(), BasePermission.READ);
Assert.assertEquals(topParent.getEntries()[0].getSid(), new PrincipalSid("ben"));
Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries()[0]).isAuditFailure());
Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries()[0]).isAuditSuccess());
Assert.assertTrue(((AuditableAccessControlEntry) topParent.getEntries()[0]).isGranting());
Assert.assertEquals(topParent.getEntries()[1].getId(), new Long(2));
Assert.assertEquals(topParent.getEntries()[1].getPermission(), BasePermission.WRITE);
Assert.assertEquals(topParent.getEntries()[1].getSid(), new PrincipalSid("ben"));
Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries()[1]).isAuditFailure());
Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries()[1]).isAuditSuccess());
Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries()[1]).isGranting());
Assert.assertTrue(middleParent.isEntriesInheriting());
Assert.assertEquals(middleParent.getId(), new Long(2));
Assert.assertEquals(middleParent.getOwner(), new PrincipalSid("ben"));
Assert.assertEquals(middleParent.getEntries()[0].getId(), new Long(3));
Assert.assertEquals(middleParent.getEntries()[0].getPermission(), BasePermission.DELETE);
Assert.assertEquals(middleParent.getEntries()[0].getSid(), new PrincipalSid("ben"));
Assert.assertFalse(((AuditableAccessControlEntry) middleParent.getEntries()[0]).isAuditFailure());
Assert.assertFalse(((AuditableAccessControlEntry) middleParent.getEntries()[0]).isAuditSuccess());
Assert.assertTrue(((AuditableAccessControlEntry) middleParent.getEntries()[0]).isGranting());
Assert.assertTrue(child.isEntriesInheriting());
Assert.assertEquals(child.getId(), new Long(3));
Assert.assertEquals(child.getOwner(), new PrincipalSid("ben"));
Assert.assertEquals(child.getEntries()[0].getId(), new Long(4));
Assert.assertEquals(child.getEntries()[0].getPermission(), BasePermission.DELETE);
Assert.assertEquals(child.getEntries()[0].getSid(), new PrincipalSid("ben"));
Assert.assertFalse(((AuditableAccessControlEntry) child.getEntries()[0]).isAuditFailure());
Assert.assertFalse(((AuditableAccessControlEntry) child.getEntries()[0]).isAuditSuccess());
Assert.assertFalse(((AuditableAccessControlEntry) child.getEntries()[0]).isGranting());
}
@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("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity middleParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
ObjectIdentity middleParent2Oid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(103));
// Retrieve the child
Map map = this.strategy.readAclsById(new ObjectIdentity[] { childOid }, null);
// Check that the child and all its parents were retrieved
Assert.assertNotNull(map.get(childOid));
Assert.assertEquals(childOid, ((Acl) map.get(childOid)).getObjectIdentity());
Assert.assertNotNull(map.get(middleParentOid));
Assert.assertEquals(middleParentOid, ((Acl) map.get(middleParentOid)).getObjectIdentity());
Assert.assertNotNull(map.get(topParentOid));
Assert.assertEquals(topParentOid, ((Acl) map.get(topParentOid)).getObjectIdentity());
// The second parent shouldn't have been retrieved
Assert.assertNull(map.get(middleParent2Oid));
}
/**
* 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("org.springframework.security.TargetObject", new Long(104));
ObjectIdentity parent1Oid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(105));
ObjectIdentity parent2Oid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(106));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(107));
// First lookup only child, thus populating the cache with grandParent, parent1 and child
Permission[] checkPermission = new Permission[] { BasePermission.READ };
Sid[] sids = new Sid[] { new PrincipalSid("ben") };
ObjectIdentity[] childOids = new ObjectIdentity[] { childOid };
((BasicLookupStrategy) this.strategy).setBatchSize(6);
Map foundAcls = strategy.readAclsById(childOids, sids);
Acl foundChildAcl = (Acl) foundAcls.get(childOid);
Assert.assertNotNull(foundChildAcl);
Assert.assertTrue(foundChildAcl.isGranted(checkPermission, sids, false));
// 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
ObjectIdentity[] allOids = new ObjectIdentity[] { grandParentOid, parent1Oid, parent2Oid, childOid };
try {
foundAcls = strategy.readAclsById(allOids, sids);
Assert.assertTrue(true);
} catch (NotFoundException notExpected) {
Assert.fail("It shouldn't have thrown NotFoundException");
}
Acl foundParent2Acl = (Acl) foundAcls.get(parent2Oid);
Assert.assertNotNull(foundParent2Acl);
Assert.assertTrue(foundParent2Acl.isGranted(checkPermission, sids, false));
}*/
@Test
public void testAclsWithDifferentSerializableTypesAsObjectIdentities() 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_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 oid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Integer(104));
Sid[] sids = new Sid[] { new PrincipalSid("ben") };
ObjectIdentity[] childOids = new ObjectIdentity[] { oid };
try {
Map foundAcls = strategy.readAclsById(childOids, sids);
Assert.fail("It should have thrown IllegalArgumentException");
} catch(IllegalArgumentException expected) {
Assert.assertTrue(true);
}
}
}
@@ -1,47 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import java.io.IOException;
import javax.sql.DataSource;
/**
* Seeds the database for {@link JdbcAclServiceTests}.
*
* @author Ben Alex
* @version $Id$
*/
public class DatabaseSeeder {
//~ Constructors ===================================================================================================
public DatabaseSeeder(DataSource dataSource, Resource resource)
throws IOException {
Assert.notNull(dataSource, "dataSource required");
Assert.notNull(resource, "resource required");
JdbcTemplate template = new JdbcTemplate(dataSource);
String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
template.execute(sql);
}
}
@@ -1,187 +0,0 @@
package org.springframework.security.acls.jdbc;
import java.io.Serializable;
import junit.framework.Assert;
import junit.framework.TestCase;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Ehcache;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.MockApplicationContext;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.TestingAuthenticationToken;
/**
* Tests {@link EhCacheBasedAclCache}
*
* @author Andrei Stefan
*/
public class EhCacheBasedAclCacheTests extends TestCase {
//~ Instance fields ================================================================================================
AbstractXmlApplicationContext ctx;
//~ Methods ========================================================================================================
private Ehcache getCache() {
this.ctx = (AbstractXmlApplicationContext) MockApplicationContext.getContext();
return (Ehcache) ctx.getBean("eHCacheBackend");
}
protected void tearDown() throws Exception {
super.tearDown();
SecurityContextHolder.clearContext();
if (ctx != null) {
ctx.close();
}
}
public void testConstructorRejectsNullParameters() throws Exception {
try {
AclCache aclCache = new EhCacheBasedAclCache(null);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
}
public void testMethodsRejectNullParameters() throws Exception {
Ehcache cache = new MockEhcache();
EhCacheBasedAclCache myCache = new EhCacheBasedAclCache(cache);
try {
Serializable id = null;
myCache.evictFromCache(id);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
ObjectIdentity obj = null;
myCache.evictFromCache(obj);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
Serializable id = null;
myCache.getFromCache(id);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
ObjectIdentity obj = null;
myCache.getFromCache(obj);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
try {
MutableAcl acl = null;
myCache.putInCache(acl);
Assert.fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
Assert.assertTrue(true);
}
}
public void testCacheOperationsAclWithoutParent() throws Exception {
Ehcache cache = getCache();
EhCacheBasedAclCache myCache = new EhCacheBasedAclCache(cache);
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
MutableAcl acl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
myCache.putInCache(acl);
Assert.assertEquals(cache.getSize(), 2);
// Check we can get from cache the same objects we put in
Assert.assertEquals(myCache.getFromCache(new Long(1)), acl);
Assert.assertEquals(myCache.getFromCache(identity), acl);
// Put another object in cache
ObjectIdentity identity2 = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
MutableAcl acl2 = new AclImpl(identity2, new Long(2), aclAuthorizationStrategy, new ConsoleAuditLogger());
myCache.putInCache(acl2);
Assert.assertEquals(cache.getSize(), 4);
// Try to evict an entry that doesn't exist
myCache.evictFromCache(new Long(3));
myCache.evictFromCache(new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102)));
Assert.assertEquals(cache.getSize(), 4);
myCache.evictFromCache(new Long(1));
Assert.assertEquals(cache.getSize(), 2);
// Check the second object inserted
Assert.assertEquals(myCache.getFromCache(new Long(2)), acl2);
Assert.assertEquals(myCache.getFromCache(identity2), acl2);
myCache.evictFromCache(identity2);
Assert.assertEquals(cache.getSize(), 0);
}
public void testCacheOperationsAclWithParent() throws Exception {
Ehcache cache = getCache();
EhCacheBasedAclCache myCache = new EhCacheBasedAclCache(cache);
Authentication auth = new TestingAuthenticationToken("user", "password", new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_GENERAL") });
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity identity = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity identityParent = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] {
new GrantedAuthorityImpl("ROLE_OWNERSHIP"), new GrantedAuthorityImpl("ROLE_AUDITING"),
new GrantedAuthorityImpl("ROLE_GENERAL") });
MutableAcl acl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
MutableAcl parentAcl = new AclImpl(identityParent, new Long(2), aclAuthorizationStrategy, new ConsoleAuditLogger());
acl.setParent(parentAcl);
myCache.putInCache(acl);
Assert.assertEquals(cache.getSize(), 4);
// Check we can get from cache the same objects we put in
Assert.assertEquals(myCache.getFromCache(new Long(1)), acl);
Assert.assertEquals(myCache.getFromCache(identity), acl);
Assert.assertEquals(myCache.getFromCache(new Long(2)), parentAcl);
Assert.assertEquals(myCache.getFromCache(identityParent), parentAcl);
}
//~ Inner Classes ==================================================================================================
private class MockEhcache extends Cache {
public MockEhcache() {
super("cache", 0, true, true, 0, 0);
}
}
}
@@ -1,425 +0,0 @@
/* 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.springframework.security.acls.jdbc;
import java.util.Map;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.acls.AccessControlEntry;
import org.springframework.security.acls.AlreadyExistsException;
import org.springframework.security.acls.ChildrenExistException;
import org.springframework.security.acls.MutableAcl;
import org.springframework.security.acls.NotFoundException;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.objectidentity.ObjectIdentity;
import org.springframework.security.acls.objectidentity.ObjectIdentityImpl;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.TestingAuthenticationToken;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
/**
* Integration tests the ACL system using an in-memory database.
*
* @author Ben Alex
* @author Andrei Stefan
* @version $Id:JdbcAclServiceTests.java 1754 2006-11-17 02:01:21Z benalex $
*/
public class JdbcAclServiceTests extends AbstractTransactionalDataSourceSpringContextTests {
//~ Constant fields ================================================================================================
public static final String SELECT_ALL_CLASSES = "SELECT * FROM acl_class WHERE class = ?";
public static final String SELECT_ALL_OBJECT_IDENTITIES = "SELECT * FROM acl_object_identity";
public static final String SELECT_OBJECT_IDENTITY = "SELECT * FROM acl_object_identity WHERE object_id_identity = ?";
public static final String SELECT_ACL_ENTRY = "SELECT * FROM acl_entry, acl_object_identity WHERE " +
"acl_object_identity.id = acl_entry.acl_object_identity " +
"AND acl_object_identity.object_id_identity <= ?";
//~ Instance fields ================================================================================================
private JdbcMutableAclService jdbcMutableAclService;
private AclCache aclCache;
private LookupStrategy lookupStrategy;
//~ Methods ========================================================================================================
protected String[] getConfigLocations() {
return new String[] {"classpath:org/springframework/security/acls/jdbc/applicationContext-test.xml"};
}
public void setJdbcMutableAclService(JdbcMutableAclService jdbcAclService) {
this.jdbcMutableAclService = jdbcAclService;
}
public void setAclCache(AclCache aclCache) {
this.aclCache = aclCache;
}
public void setLookupStrategy(LookupStrategy lookupStrategy) {
this.lookupStrategy = lookupStrategy;
}
protected void onTearDown() throws Exception {
super.onTearDown();
SecurityContextHolder.clearContext();
}
public void testLifecycle() {
setComplete();
Authentication auth = new TestingAuthenticationToken("ben", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ADMINISTRATOR")});
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity middleParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
MutableAcl child = jdbcMutableAclService.createAcl(childOid);
// Specify the inheritence hierarchy
middleParent.setParent(topParent);
child.setParent(middleParent);
// Now let's add a couple of permissions
topParent.insertAce(null, BasePermission.READ, new PrincipalSid(auth), true);
topParent.insertAce(null, BasePermission.WRITE, new PrincipalSid(auth), false);
middleParent.insertAce(null, BasePermission.DELETE, new PrincipalSid(auth), true);
child.insertAce(null, BasePermission.DELETE, new PrincipalSid(auth), false);
// Explictly save the changed ACL
jdbcMutableAclService.updateAcl(topParent);
jdbcMutableAclService.updateAcl(middleParent);
jdbcMutableAclService.updateAcl(child);
// Let's check if we can read them back correctly
Map map = jdbcMutableAclService.readAclsById(new ObjectIdentity[] {topParentOid, middleParentOid, childOid});
assertEquals(3, map.size());
// Replace our current objects with their retrieved versions
topParent = (MutableAcl) map.get(topParentOid);
middleParent = (MutableAcl) map.get(middleParentOid);
child = (MutableAcl) map.get(childOid);
// Check the retrieved versions has IDs
assertNotNull(topParent.getId());
assertNotNull(middleParent.getId());
assertNotNull(child.getId());
// Check their parents were correctly persisted
assertNull(topParent.getParentAcl());
assertEquals(topParentOid, middleParent.getParentAcl().getObjectIdentity());
assertEquals(middleParentOid, child.getParentAcl().getObjectIdentity());
// Check their ACEs were correctly persisted
assertEquals(2, topParent.getEntries().length);
assertEquals(1, middleParent.getEntries().length);
assertEquals(1, child.getEntries().length);
// Check the retrieved rights are correct
assertTrue(topParent.isGranted(new Permission[] {BasePermission.READ}, new Sid[] {new PrincipalSid(auth)}, false));
assertFalse(topParent.isGranted(new Permission[] {BasePermission.WRITE}, new Sid[] {new PrincipalSid(auth)},
false));
assertTrue(middleParent.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)},
false));
assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, false));
try {
child.isGranted(new Permission[] {BasePermission.ADMINISTRATION}, new Sid[] {new PrincipalSid(auth)}, false);
fail("Should have thrown NotFoundException");
} catch (NotFoundException expected) {
assertTrue(true);
}
// Now check the inherited rights (when not explicitly overridden) also look OK
assertTrue(child.isGranted(new Permission[] {BasePermission.READ}, new Sid[] {new PrincipalSid(auth)}, false));
assertFalse(child.isGranted(new Permission[] {BasePermission.WRITE}, new Sid[] {new PrincipalSid(auth)}, false));
assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, false));
// Next change the child so it doesn't inherit permissions from above
child.setEntriesInheriting(false);
jdbcMutableAclService.updateAcl(child);
child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
assertFalse(child.isEntriesInheriting());
// Check the child permissions no longer inherit
assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, true));
try {
child.isGranted(new Permission[] {BasePermission.READ}, new Sid[] {new PrincipalSid(auth)}, true);
fail("Should have thrown NotFoundException");
} catch (NotFoundException expected) {
assertTrue(true);
}
try {
child.isGranted(new Permission[] {BasePermission.WRITE}, new Sid[] {new PrincipalSid(auth)}, true);
fail("Should have thrown NotFoundException");
} catch (NotFoundException expected) {
assertTrue(true);
}
// Let's add an identical permission to the child, but it'll appear AFTER the current permission, so has no impact
child.insertAce(null, BasePermission.DELETE, new PrincipalSid(auth), true);
// Let's also add another permission to the child
child.insertAce(null, BasePermission.CREATE, new PrincipalSid(auth), true);
// Save the changed child
jdbcMutableAclService.updateAcl(child);
child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
assertEquals(3, child.getEntries().length);
// Output permissions
for (int i = 0; i < child.getEntries().length; i++) {
System.out.println(child.getEntries()[i]);
}
// Check the permissions are as they should be
assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, true)); // as earlier permission overrode
assertTrue(child.isGranted(new Permission[] {BasePermission.CREATE}, new Sid[] {new PrincipalSid(auth)}, true));
// Now check the first ACE (index 0) really is DELETE for our Sid and is non-granting
AccessControlEntry entry = child.getEntries()[0];
assertEquals(BasePermission.DELETE.getMask(), entry.getPermission().getMask());
assertEquals(new PrincipalSid(auth), entry.getSid());
assertFalse(entry.isGranting());
assertNotNull(entry.getId());
// Now delete that first ACE
child.deleteAce(entry.getId());
// Save and check it worked
child = jdbcMutableAclService.updateAcl(child);
assertEquals(2, child.getEntries().length);
assertTrue(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, false));
SecurityContextHolder.clearContext();
}
/**
* Test method that demonstrates eviction failure from cache - SEC-676
*/
/* public void testDeleteAclAlsoDeletesChildren() throws Exception {
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity middleParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
// Delete the mid-parent and test if the child was deleted, as well
jdbcMutableAclService.deleteAcl(middleParentOid, true);
try {
Acl acl = jdbcMutableAclService.readAclById(middleParentOid);
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
try {
Acl acl = jdbcMutableAclService.readAclById(childOid);
fail("It should have thrown NotFoundException");
}
catch (NotFoundException expected) {
assertTrue(true);
}
Acl acl = jdbcMutableAclService.readAclById(topParentOid);
assertNotNull(acl);
assertEquals(((MutableAcl) acl).getObjectIdentity(), topParentOid);
}*/
public void testConstructorRejectsNullParameters() throws Exception {
try {
JdbcAclService service = new JdbcMutableAclService(null, lookupStrategy, aclCache);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
JdbcAclService service = new JdbcMutableAclService(this.getJdbcTemplate().getDataSource(), null, aclCache);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
JdbcAclService service = new JdbcMutableAclService(this.getJdbcTemplate().getDataSource(), lookupStrategy, null);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testCreateAclRejectsNullParameter() throws Exception {
try {
jdbcMutableAclService.createAcl(null);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testCreateAclForADuplicateDomainObject() throws Exception {
ObjectIdentity duplicateOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
// Try to add the same object second time
try {
jdbcMutableAclService.createAcl(duplicateOid);
fail("It should have thrown AlreadyExistsException");
}
catch (AlreadyExistsException expected) {
assertTrue(true);
}
}
public void testDeleteAclRejectsNullParameters() throws Exception {
try {
jdbcMutableAclService.deleteAcl(null, true);
fail("It should have thrown IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testDeleteAclWithChildrenThrowsException() throws Exception {
try {
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
jdbcMutableAclService.deleteAcl(topParentOid, false);
fail("It should have thrown ChildrenExistException");
}
catch (ChildrenExistException expected) {
assertTrue(true);
}
}
public void testDeleteAllAclsRemovesAclClassRecord() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ADMINISTRATOR")});
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
// Remove all acls associated with a certain class type
jdbcMutableAclService.deleteAcl(topParentOid, true);
// Check the acl_class table is empty
assertEquals(0, getJdbcTemplate().queryForList(SELECT_ALL_CLASSES, new Object[] {"org.springframework.security.TargetObject"} ).size());
}
public void testDeleteAclRemovesRowsFromDatabase() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ADMINISTRATOR")});
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(100));
ObjectIdentity middleParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(101));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(102));
// Remove the child and check all related database rows were removed accordingly
jdbcMutableAclService.deleteAcl(childOid, false);
assertEquals(1, getJdbcTemplate().queryForList(SELECT_ALL_CLASSES, new Object[] {"org.springframework.security.TargetObject"} ).size());
assertEquals(0, getJdbcTemplate().queryForList(SELECT_OBJECT_IDENTITY, new Object[] {new Long(102)}).size());
assertEquals(2, getJdbcTemplate().queryForList(SELECT_ALL_OBJECT_IDENTITIES).size());
assertEquals(3, getJdbcTemplate().queryForList(SELECT_ACL_ENTRY, new Object[] {new Long(103)} ).size());
// Check the cache
assertNull(aclCache.getFromCache(childOid));
assertNull(aclCache.getFromCache(new Long(102)));
}
/**
* SEC-655
*/
/* public void testClearChildrenFromCacheWhenParentIsUpdated() throws Exception {
Authentication auth = new TestingAuthenticationToken("ben", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ADMINISTRATOR")});
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity parentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(104));
ObjectIdentity childOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(105));
MutableAcl parent = jdbcMutableAclService.createAcl(parentOid);
MutableAcl child = jdbcMutableAclService.createAcl(childOid);
child.setParent(parent);
jdbcMutableAclService.updateAcl(child);
parent = (AclImpl) jdbcMutableAclService.readAclById(parentOid);
parent.insertAce(null, BasePermission.READ, new PrincipalSid("ben"), true);
jdbcMutableAclService.updateAcl(parent);
parent = (AclImpl) jdbcMutableAclService.readAclById(parentOid);
parent.insertAce(null, BasePermission.READ, new PrincipalSid("scott"), true);
jdbcMutableAclService.updateAcl(parent);
child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
parent = (MutableAcl) child.getParentAcl();
assertEquals("Fails because child has a stale reference to its parent", 2, parent.getEntries().length);
assertEquals(1, parent.getEntries()[0].getPermission().getMask());
assertEquals(new PrincipalSid("ben"), parent.getEntries()[0].getSid());
assertEquals(1, parent.getEntries()[1].getPermission().getMask());
assertEquals(new PrincipalSid("scott"), parent.getEntries()[1].getSid());
}*/
/* public void testCumulativePermissions() {
setComplete();
Authentication auth = new TestingAuthenticationToken("ben", "ignored", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ADMINISTRATOR")});
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity topParentOid = new ObjectIdentityImpl("org.springframework.security.TargetObject", new Long(110));
MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
// Add an ACE permission entry
CumulativePermission cm = new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION);
assertEquals(17, cm.getMask());
topParent.insertAce(null, cm, new PrincipalSid(auth), true);
assertEquals(1, topParent.getEntries().length);
// Explictly save the changed ACL
topParent = jdbcMutableAclService.updateAcl(topParent);
// Check the mask was retrieved correctly
assertEquals(17, topParent.getEntries()[0].getPermission().getMask());
assertTrue(topParent.isGranted(new Permission[] {cm}, new Sid[] {new PrincipalSid(auth)}, true));
SecurityContextHolder.clearContext();
}
*/
}
@@ -1,37 +0,0 @@
package org.springframework.security.acls.objectidentity;
import junit.framework.TestCase;
/**
* Tests for {@link ObjectIdentityRetrievalStrategyImpl}
*
* @author Andrei Stefan
*/
public class ObjectIdentityRetrievalStrategyImplTests extends TestCase {
//~ Methods ========================================================================================================
public void testObjectIdentityCreation() throws Exception {
MockIdDomainObject domain = new MockIdDomainObject();
domain.setId(new Integer(1));
ObjectIdentityRetrievalStrategy retStrategy = new ObjectIdentityRetrievalStrategyImpl();
ObjectIdentity identity = retStrategy.getObjectIdentity(domain);
assertNotNull(identity);
assertEquals(identity, new ObjectIdentityImpl(domain));
}
//~ Inner Classes ==================================================================================================
private class MockIdDomainObject {
private Object id;
public Object getId() {
return id;
}
public void setId(Object id) {
this.id = id;
}
}
}

Some files were not shown because too many files have changed in this diff Show More