瀏覽代碼

SEC-354: Add label-based voter.

Ben Alex 19 年之前
父節點
當前提交
1b4a098760

+ 74 - 0
core/src/main/java/org/acegisecurity/vote/InterfaceBasedLabelParameterStrategy.java

@@ -0,0 +1,74 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.vote;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * This is a very useful implementation of the LabelParameterStrategy. Data objects which are meant to be labeled
+ * should implement the LabeledData interface. This strategy will then castdown to that interface for either testing
+ * or retrieval of the label.
+ *
+ * @author Greg Turnquist
+ * @version $Id$
+ */
+public class InterfaceBasedLabelParameterStrategy implements LabelParameterStrategy {
+    //~ Instance fields ================================================================================================
+
+    private String noLabel = "";
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * Test if the argument is labeled, and if so, downcast to LabeledData and retrieve the domain object's
+     * labeled value. Otherwise, return an empty string. NOTE: The default for no label is an empty string. If somehow
+     * the user wants to make that a label itself, he or she must inject an alternate value to the noLabel property.
+     *
+     * @param method DOCUMENT ME!
+     * @param arg DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     */
+    public String getLabel(Method method, Object arg) {
+        if (isLabeled(method, arg)) {
+            return ((LabeledData) arg).getLabel();
+        } else {
+            return noLabel;
+        }
+    }
+
+    public String getNoLabel() {
+        return noLabel;
+    }
+
+    /**
+     * Test if the argument implemented the LabeledData interface. NOTE: The invoking method has no bearing for
+     * this strategy, only the argument itself.
+     *
+     * @param method DOCUMENT ME!
+     * @param arg DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     */
+    public boolean isLabeled(Method method, Object arg) {
+        return (arg instanceof LabeledData);
+    }
+
+    public void setNoLabel(String noLabel) {
+        this.noLabel = noLabel;
+    }
+}

+ 288 - 0
core/src/main/java/org/acegisecurity/vote/LabelBasedAclVoter.java

@@ -0,0 +1,288 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.vote;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.ConfigAttribute;
+import org.acegisecurity.ConfigAttributeDefinition;
+
+import org.acegisecurity.vote.AbstractAclVoter;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * <p>This Acl voter will evaluate methods based on labels applied to incoming arguments. It will only check
+ * methods that have been properly tagged in the MethodSecurityInterceptor with the value stored in
+ * <b>attributeIndicatingLabeledOperation</b>. If a method has been tagged, then it examines each argument, and if the
+ * argument implements {@link LabeledData}, then it will asses if the user's list of granted authorities matches.</p>
+ *  <p>By default, if none of the arguments are labeled, then the access will be granted. This can be overridden by
+ * setting <b>allowAccessIfNoAttributesAreLabeled</b> to false in the Spring context file.</p>
+ *  <p>In many situations, different values are linked together to define a common label, it is  necessary to
+ * define a map in the application context that links user-assigned label access to domain object labels. This is done
+ * by setting up the <b>labelMap</b> in the application context.</p>
+ *
+ * @author Greg Turnquist
+ * @version $Id$
+ *
+ * @see org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor
+ */
+public class LabelBasedAclVoter extends AbstractAclVoter {
+    //~ Instance fields ================================================================================================
+
+    private HashMap labelMap = null;
+    Log logger = LogFactory.getLog(LabelBasedAclVoter.class);
+    private String attributeIndicatingLabeledOperation = null;
+    private boolean allowAccessIfNoAttributesAreLabeled = true;
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * Set whether or not to allow the user to run methods in which none of the incoming arguments are labeled.<p>Default
+     * value: <b>true, users can run such methods.</b></p>
+     *
+     * @param allowAccessIfNoAttributesAreLabeled boolean
+     */
+    public void setAllowAccessIfNoAttributesAreLabeled(boolean allowAccessIfNoAttributesAreLabeled) {
+        this.allowAccessIfNoAttributesAreLabeled = allowAccessIfNoAttributesAreLabeled;
+    }
+
+    /**
+     * Each method intended for evaluation by this voter must include this tag name in the definition of the
+     * MethodSecurityInterceptor, indicating if this voter should evaluate the arguments and compare them against the
+     * label map.
+     *
+     * @param attributeIndicatingLabeledOperation string
+     */
+    public void setAttributeIndicatingLabeledOperation(String attributeIndicatingLabeledOperation) {
+        this.attributeIndicatingLabeledOperation = attributeIndicatingLabeledOperation;
+    }
+
+    /**
+     * Set the map that correlate a user's assigned label against domain object values that are considered data
+     * labels.<p></p>
+     *
+     * @param labelMap - HashMap  Example application context configuration of a labelMap:
+     * 
+     * <pre>
+	 *  &lt;bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased"&gt;<br>
+	 *		&lt;property name="allowIfAllAbstainDecisions">&lt;value&gt;false&lt;/value&gt;&lt;/property&gt;<br>
+	 *		&lt;property name="decisionVoters"&gt;<br>
+	 *			&lt;list&gt;<br>
+	 *				&lt;bean class="org.acegisecurity.vote.RoleVoter"/&gt;<br>
+	 *				&lt;bean class="net.homelinux.scifi.LabelBasedAclVoter"&gt;<br>
+	 *					&lt;property name="attributeIndicatingLabeledOperation"&gt;&lt;value&gt;LABELED_OPERATION&lt;/value&gt;&lt;/property&gt;<br>
+	 *					&lt;property name="labelMap"&gt;<br>
+	 *						&lt;map&gt;<br>
+	 *							&lt;entry key="DATA_LABEL_BLUE"&gt;<br>
+	 *								&lt;list&gt;<br>
+	 *									&lt;value&gt;blue&lt;/value&gt;<br>
+	 *									&lt;value&gt;indigo&lt;/value&gt;<br>
+	 *									&lt;value&gt;purple&lt;/value&gt;<br>
+	 *								&lt;/list&gt;<br>
+	 *							&lt;/entry&gt;<br>
+	 *							&lt;entry key="LABEL_ORANGE"&gt;<br>
+	 *								&lt;list&gt;<br>
+	 *									&lt;value&gt;orange&lt;/value&gt;<br>
+	 *									&lt;value&gt;sunshine&lt;/value&gt;<br>
+	 *									&lt;value&gt;amber&lt;/value&gt;<br>
+	 *								&lt;/list&gt;<br>
+	 *							&lt;/entry&gt;<br>
+	 *							&lt;entry key="LABEL_ADMIN"&gt;<br>
+	 *								&lt;list&gt;<br>
+	 *									&lt;value&gt;blue&lt;/value&gt;<br>
+	 *									&lt;value&gt;indigo&lt;/value&gt;<br>
+	 *									&lt;value&gt;purple&lt;/value&gt;<br>
+	 *									&lt;value&gt;orange&lt;/value&gt;<br>
+	 *									&lt;value&gt;sunshine&lt;/value&gt;<br>
+	 *									&lt;value&gt;amber&lt;/value&gt;<br>
+	 *								&lt;/list&gt;<br>
+	 *							&lt;/entry&gt;<br>
+	 *						&lt;/map&gt;<br>
+	 *					&lt;/property&gt;<br>
+	 *				&lt;/bean&gt;<br>
+	 *			&lt;/list&gt;<br>
+	 *		&lt;/property&gt;<br>
+	 *	&lt;/bean&gt;<br>
+	 * </pre>
+	 */
+    public void setLabelMap(HashMap labelMap) {
+        this.labelMap = labelMap;
+    }
+
+    /**
+     * This acl voter will only evaluate labeled methods if they are marked in the security interceptor's
+     * configuration with the attribute stored in attributeIndicatingLabeledOperation.
+     *
+     * @param attribute DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @see org.acegisecurity.vote.AbstractAclVoter
+     * @see org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor
+     */
+    public boolean supports(ConfigAttribute attribute) {
+        if (attribute.getAttribute().equals(attributeIndicatingLabeledOperation)) {
+            logger.debug(attribute + " is supported.");
+
+            return true;
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(attribute + " is unsupported.");
+        }
+
+        return false;
+    }
+
+    /**
+     * Vote on whether or not the user has all the labels necessary to match the method argument's labeled
+     * data.
+     *
+     * @param authentication DOCUMENT ME!
+     * @param object DOCUMENT ME!
+     * @param config DOCUMENT ME!
+     *
+     * @return ACCESS_ABSTAIN, ACCESS_GRANTED, or ACCESS_DENIED.
+     */
+    public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
+        int result = ACCESS_ABSTAIN;
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("==========================================================");
+        }
+
+        if (this.supports((ConfigAttribute) config.getConfigAttributes().next())) {
+            result = ACCESS_DENIED;
+
+            /* Parse out the user's labels by examining the security context, and checking
+             * for matches against the label map.
+             */
+            List userLabels = new Vector();
+
+            for (int i = 0; i < authentication.getAuthorities().length; i++) {
+                if (labelMap.containsKey(authentication.getAuthorities()[i].getAuthority())) {
+                    String userLabel = authentication.getAuthorities()[i].getAuthority();
+                    userLabels.add(userLabel);
+                    logger.debug("Adding " + userLabel + " to <<<" + authentication.getName()
+                        + "'s>>> authorized label list");
+                }
+            }
+
+            MethodInvocation invocation = (MethodInvocation) object;
+
+            int matches = 0;
+            int misses = 0;
+            int labeledArguments = 0;
+
+            for (int j = 0; j < invocation.getArguments().length; j++) {
+                if (invocation.getArguments()[j] instanceof LabeledData) {
+                    labeledArguments++;
+
+                    boolean matched = false;
+
+                    String argumentDataLabel = ((LabeledData) invocation.getArguments()[j]).getLabel();
+                    logger.debug("Argument[" + j + "/" + invocation.getArguments()[j].getClass().getName()
+                        + "] has a data label of " + argumentDataLabel);
+
+                    List validDataLabels = new Vector();
+
+                    for (int i = 0; i < userLabels.size(); i++) {
+                        validDataLabels.addAll((List) labelMap.get(userLabels.get(i)));
+                    }
+
+                    logger.debug("The valid labels for user label " + userLabels + " are " + validDataLabels);
+
+                    Iterator dataLabelIter = validDataLabels.iterator();
+
+                    while (dataLabelIter.hasNext()) {
+                        String validDataLabel = (String) dataLabelIter.next();
+
+                        if (argumentDataLabel.equals(validDataLabel)) {
+                            logger.debug(userLabels + " maps to " + validDataLabel + " which matches the argument");
+                            matched = true;
+                        }
+                    }
+
+                    if (matched) {
+                        logger.debug("We have a match!");
+                        matches++;
+                    } else {
+                        logger.debug("We have a miss!");
+                        misses++;
+                    }
+                } /* if arguments is an ILabel */} /* loop through all arguments */
+            Assert.isTrue((matches + misses) == labeledArguments,
+                "The matches (" + matches + ") and misses (" + misses + " ) don't add up (" + labeledArguments + ")");
+
+            logger.debug("We have " + matches + " matches and " + misses + " misses and " + labeledArguments
+                + " labeled arguments.");
+
+            /* The result has already been set to ACCESS_DENIED. Only if there is a proper match of
+             * labels will this be overturned. However, if none of the attributes are actually labeled,
+             * the result is dependent on allowAccessIfNoAttributesAreLabeled.
+             */
+            if ((matches > 0) && (misses == 0)) {
+                result = ACCESS_GRANTED;
+            } else if (labeledArguments == 0) {
+                if (allowAccessIfNoAttributesAreLabeled) {
+                    result = ACCESS_GRANTED;
+                } else {
+                    result = ACCESS_DENIED;
+                }
+            }
+        }
+
+        if (logger.isDebugEnabled()) {
+            switch (result) {
+            case ACCESS_GRANTED:
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("===== Access is granted =====");
+                }
+
+                break;
+
+            case ACCESS_DENIED:
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("===== Access is denied =====");
+                }
+
+                break;
+
+            case ACCESS_ABSTAIN:
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("===== Abstaining =====");
+                }
+
+                break;
+            }
+        }
+
+        return result;
+    }
+}

+ 52 - 0
core/src/main/java/org/acegisecurity/vote/LabelParameterStrategy.java

@@ -0,0 +1,52 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.vote;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * This is a strategy interface for determining which parts of a method invocation
+ * are labeled. Not all arguments are necessarily labeled. This offers a plugabble
+ * mechanism to define various ways to label data.
+ *
+ * @author Greg Turnquist
+ * @version $Id$
+ */
+public interface LabelParameterStrategy {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Get the actual label associated with the argument. NOTE: This currently only supports one label per
+     * argument.
+     *
+     * @param method
+     * @param arg
+     *
+     * @return string value of the label
+     */
+    public String getLabel(Method method, Object arg);
+
+    /**
+     * Evaluate if one particular argument is labeled. The context of the method is also provided should that
+     * have bearing on the label.
+     *
+     * @param method
+     * @param arg
+     *
+     * @return boolean
+     */
+    public boolean isLabeled(Method method, Object arg);
+}

+ 35 - 0
core/src/main/java/org/acegisecurity/vote/LabeledData.java

@@ -0,0 +1,35 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.vote;
+
+/**
+ * This interface indicates data objects that carry a label. The purpose is to support
+ * the {@link LabelBasedAclVoter}. When it votes, it evaluates all method arguments
+ * tagged with this interface, and votes if they match the user's granted authorities list.
+ * 
+ * @author Greg Turnquist
+ */
+public interface LabeledData {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Retrieve the domain object's data label. NOTE: This implementation only supports one data label per
+     * object.
+     *
+     * @return The label value of data object as a string.
+     */
+    public String getLabel();
+}

+ 184 - 0
core/src/test/java/org/acegisecurity/vote/LabelBasedAclVoterTests.java

@@ -0,0 +1,184 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.vote;
+
+import org.acegisecurity.AccessDeniedException;
+import org.acegisecurity.AuthenticationManager;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
+
+import java.util.List;
+
+
+/**
+ * 
+DOCUMENT ME!
+ *
+ * @author Greg Turnquist
+ * @version $Id$
+ */
+public class LabelBasedAclVoterTests extends AbstractDependencyInjectionSpringContextTests {
+    //~ Instance fields ================================================================================================
+
+    private SampleService sampleService = null;
+
+    //~ Methods ========================================================================================================
+
+    protected String[] getConfigLocations() {
+        return new String[] {"org/acegisecurity/vote/labelBasedSecurityApplicationContext.xml"};
+    }
+
+    public SampleService getSampleService() {
+        return sampleService;
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(LabelBasedAclVoterTests.class);
+    }
+
+    public void setSampleService(SampleService sampleService) {
+        this.sampleService = sampleService;
+    }
+
+    private void setupContext(String username, String password) {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
+        AuthenticationManager authenticationManager = (AuthenticationManager) applicationContext.getBean(
+                "authenticationManager");
+        SecurityContextHolder.getContext().setAuthentication(authenticationManager.authenticate(token));
+    }
+
+    public void testDoingSomethingForBlueUser() {
+        setupContext("blueuser", "password");
+
+        List dataList = sampleService.getTheSampleData();
+        assertNotNull(dataList);
+
+        SampleBlockOfData block1 = (SampleBlockOfData) dataList.get(0);
+        SampleBlockOfData block2 = (SampleBlockOfData) dataList.get(1);
+        SampleBlockOfData block3 = (SampleBlockOfData) dataList.get(2);
+
+        sampleService.doSomethingOnThis(block1, block1);
+
+        try {
+            sampleService.doSomethingOnThis(block2, block2);
+            fail("Expected an AccessDeniedException");
+        } catch (AccessDeniedException e) {}
+        catch (RuntimeException e) {
+            fail("Expected an AccessDeniedException");
+        }
+
+        try {
+            sampleService.doSomethingOnThis(block1, block2);
+            fail("Expected an AccessDeniedException");
+        } catch (AccessDeniedException e) {}
+        catch (RuntimeException e) {
+            fail("Expected an AccessDeniedException");
+        }
+
+        try {
+            sampleService.doSomethingOnThis(block2, block1);
+            fail("Expected an AccessDeniedException");
+        } catch (AccessDeniedException e) {}
+        catch (RuntimeException e) {
+            fail("Expected an AccessDeniedException");
+        }
+
+        sampleService.doSomethingOnThis(block3, block3);
+    }
+
+    public void testDoingSomethingForMultiUser() {
+        setupContext("multiuser", "password4");
+
+        List dataList = sampleService.getTheSampleData();
+        assertNotNull(dataList);
+
+        SampleBlockOfData block1 = (SampleBlockOfData) dataList.get(0);
+        SampleBlockOfData block2 = (SampleBlockOfData) dataList.get(1);
+        SampleBlockOfData block3 = (SampleBlockOfData) dataList.get(2);
+
+        sampleService.doSomethingOnThis(block1, block1);
+        sampleService.doSomethingOnThis(block2, block2);
+        sampleService.doSomethingOnThis(block1, block2);
+        sampleService.doSomethingOnThis(block2, block1);
+        sampleService.doSomethingOnThis(block3, block3);
+    }
+
+    public void testDoingSomethingForOrangeUser() {
+        setupContext("orangeuser", "password3");
+
+        List dataList = sampleService.getTheSampleData();
+        assertNotNull(dataList);
+
+        SampleBlockOfData block1 = (SampleBlockOfData) dataList.get(0);
+        SampleBlockOfData block2 = (SampleBlockOfData) dataList.get(1);
+        SampleBlockOfData block3 = (SampleBlockOfData) dataList.get(2);
+
+        sampleService.doSomethingOnThis(block2, block2);
+
+        try {
+            sampleService.doSomethingOnThis(block1, block1);
+            fail("Expected an AccessDeniedException");
+        } catch (AccessDeniedException e) {}
+        catch (RuntimeException e) {
+            fail("Expected an AccessDeniedException");
+        }
+
+        try {
+            sampleService.doSomethingOnThis(block1, block2);
+            fail("Expected an AccessDeniedException");
+        } catch (AccessDeniedException e) {}
+        catch (RuntimeException e) {
+            fail("Expected an AccessDeniedException");
+        }
+
+        try {
+            sampleService.doSomethingOnThis(block2, block1);
+            fail("Expected an AccessDeniedException");
+        } catch (AccessDeniedException e) {}
+        catch (RuntimeException e) {
+            fail("Expected an AccessDeniedException");
+        }
+
+        sampleService.doSomethingOnThis(block3, block3);
+    }
+
+    public void testDoingSomethingForSuperUser() {
+        setupContext("superuser", "password2");
+
+        List dataList = sampleService.getTheSampleData();
+        assertNotNull(dataList);
+
+        SampleBlockOfData block1 = (SampleBlockOfData) dataList.get(0);
+        SampleBlockOfData block2 = (SampleBlockOfData) dataList.get(1);
+        SampleBlockOfData block3 = (SampleBlockOfData) dataList.get(2);
+
+        sampleService.doSomethingOnThis(block1, block1);
+        sampleService.doSomethingOnThis(block2, block2);
+        sampleService.doSomethingOnThis(block1, block2);
+        sampleService.doSomethingOnThis(block2, block1);
+        sampleService.doSomethingOnThis(block3, block3);
+    }
+
+    public void testSampleBlockOfDataPOJO() {
+        SampleBlockOfData block = new SampleBlockOfData();
+        block.setId("ID-ABC");
+        assertEquals(block.getId(), "ID-ABC");
+    }
+}

+ 67 - 0
core/src/test/java/org/acegisecurity/vote/SampleBlockOfData.java

@@ -0,0 +1,67 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.vote;
+
+import org.acegisecurity.vote.LabeledData;
+
+import java.io.Serializable;
+
+
+/**
+ * For label unit tests.
+ *
+ * @author Greg Turnquist
+ * @version $Id$
+ */
+public class SampleBlockOfData implements Serializable, LabeledData {
+    //~ Static fields/initializers =====================================================================================
+
+    private static final long serialVersionUID = 1L;
+    public static final String DATA_LABEL_BLUE = "blue";
+    public static final String DATA_LABEL_ORANGE = "orange";
+    public static final String DATA_LABEL_SHARED = "blue-orange";
+
+    //~ Instance fields ================================================================================================
+
+    private String dataType;
+    private String id;
+
+    //~ Methods ========================================================================================================
+
+    public String getId() {
+        return id;
+    }
+
+    public String getLabel() {
+        return dataType;
+    }
+
+    public String getSomeData() {
+        return dataType;
+    }
+
+    public void setId(String ticketNumber) {
+        this.id = ticketNumber;
+    }
+
+    public void setSomeData(String trafficType) {
+        this.dataType = trafficType;
+    }
+
+    public String toString() {
+        return this.id + "/" + this.dataType;
+    }
+}

+ 31 - 0
core/src/test/java/org/acegisecurity/vote/SampleService.java

@@ -0,0 +1,31 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.vote;
+
+import java.util.List;
+
+
+/**
+ * @author Greg Turnquist
+ * @version $Id$
+ */
+public interface SampleService {
+    //~ Methods ========================================================================================================
+
+    public void doSomethingOnThis(SampleBlockOfData block1, SampleBlockOfData block2);
+
+    public List getTheSampleData();
+}

+ 78 - 0
core/src/test/java/org/acegisecurity/vote/SampleServiceImpl.java

@@ -0,0 +1,78 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.vote;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.apache.log4j.Logger;
+
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * For label unit tests.
+ *
+ * @author Greg Turnquist
+ * @version $Id$
+ */
+public class SampleServiceImpl implements SampleService {
+    //~ Instance fields ================================================================================================
+
+    Logger logger = Logger.getLogger(SampleServiceImpl.class);
+
+    //~ Methods ========================================================================================================
+
+    public void doSomethingOnThis(SampleBlockOfData block1, SampleBlockOfData block2) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("You made it! Your context is " + SecurityContextHolder.getContext().getAuthentication());
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Block1 is " + block1);
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Block2 is " + block2);
+        }
+    }
+
+    public List getTheSampleData() {
+        if (logger.isDebugEnabled()) {
+            logger.debug(SecurityContextHolder.getContext().getAuthentication().getName()
+                + " is requesting some sample data.");
+        }
+
+        List dataList = new Vector();
+        SampleBlockOfData block;
+
+        block = new SampleBlockOfData();
+        block.setId("001");
+        block.setSomeData(SampleBlockOfData.DATA_LABEL_BLUE);
+        dataList.add(block);
+
+        block = new SampleBlockOfData();
+        block.setId("002");
+        block.setSomeData(SampleBlockOfData.DATA_LABEL_ORANGE);
+        dataList.add(block);
+
+        block = new SampleBlockOfData();
+        block.setId("003");
+        block.setSomeData(SampleBlockOfData.DATA_LABEL_SHARED);
+        dataList.add(block);
+
+        return dataList;
+    }
+}

+ 91 - 0
core/src/test/resources/org/acegisecurity/vote/labelBasedSecurityApplicationContext.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >
+<beans>
+	<bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
+	  <property name="userMap">
+	    <value>
+	      blueuser=password,ROLE_BASIC,LABEL_BLUE
+	      superuser=password2,ROLE_BASIC,LABEL_SHARED
+	      orangeuser=password3,ROLE_BASIC,LABEL_ORANGE
+	      multiuser=password4,ROLE_BASIC,LABEL_BLUE,LABEL_ORANGE
+	    </value>
+	  </property>
+	</bean>
+		
+	<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
+	  <property name="userDetailsService"><ref bean="userDetailsService"/></property>
+	</bean>        
+	
+	<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
+	  <property name="providers">
+	    <list>
+	      <ref local="daoAuthenticationProvider"/>
+	    </list>
+	  </property>
+	</bean>
+
+	<bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
+		<property name="allowIfAllAbstainDecisions"><value>false</value></property>
+		<property name="decisionVoters">
+			<list>
+				<bean class="org.acegisecurity.vote.RoleVoter"/>
+				<bean class="org.acegisecurity.vote.LabelBasedAclVoter">
+					<property name="attributeIndicatingLabeledOperation"><value>LABELED_OPERATION</value></property>
+					<property name="labelMap">
+						<map>
+							<entry key="LABEL_BLUE">
+								<list>
+									<value>blue</value>
+									<value>blue-orange</value>
+								</list>
+							</entry>
+							<entry key="LABEL_ORANGE">
+								<list>
+									<value>orange</value>
+									<value>blue-orange</value>
+								</list>
+							</entry>
+							<entry key="LABEL_SHARED">
+								<list>
+									<value>blue</value>
+									<value>orange</value>
+									<value>blue-orange</value>
+								</list>
+							</entry>
+						</map>
+					</property>
+				</bean>
+			</list>
+		</property>
+	</bean>
+	
+	<bean id="securityInteceptor"
+		class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
+	  <property name="validateConfigAttributes"><value>false</value></property>
+	  <property name="authenticationManager"><ref bean="authenticationManager"/></property>
+	  <property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
+	  <property name="objectDefinitionSource">
+	    <value>
+	      org.acegisecurity.vote.SampleService.get*=ROLE_BASIC
+	      org.acegisecurity.vote.SampleService.do*=ROLE_BASIC,LABELED_OPERATION
+	    </value>
+	  </property>
+	</bean>
+	
+    <bean id="perfOfSecurity" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor">
+    	<property name="prefix"><value>Security: </value></property>
+    </bean>
+    
+	<bean id="sampleService" class="org.acegisecurity.vote.SampleServiceImpl"/>
+	
+ 	<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
+	  <property name="beanNames"><value>sampleService</value></property>
+	  <property name="interceptorNames">
+	    <list>
+	      <value>perfOfSecurity</value>
+	      <value>securityInteceptor</value>
+	    </list>
+	  </property>
+	</bean>
+
+</beans>