瀏覽代碼

Refactor Contacts Sample to use new ACL security.

Ben Alex 21 年之前
父節點
當前提交
6e687d47d4
共有 39 個文件被更改,包括 1780 次插入1036 次删除
  1. 3 1
      changelog.txt
  2. 59 0
      samples/contacts/src/main/java/sample/contact/AddPermission.java
  3. 162 0
      samples/contacts/src/main/java/sample/contact/AddPermissionController.java
  4. 66 0
      samples/contacts/src/main/java/sample/contact/AddPermissionValidator.java
  5. 92 0
      samples/contacts/src/main/java/sample/contact/AdminPermissionController.java
  6. 15 12
      samples/contacts/src/main/java/sample/contact/ClientApplication.java
  7. 7 22
      samples/contacts/src/main/java/sample/contact/Contact.java
  8. 75 0
      samples/contacts/src/main/java/sample/contact/ContactDao.java
  9. 309 0
      samples/contacts/src/main/java/sample/contact/ContactDaoSpring.java
  10. 13 5
      samples/contacts/src/main/java/sample/contact/ContactManager.java
  11. 60 114
      samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java
  12. 0 149
      samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java
  13. 0 117
      samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java
  14. 153 0
      samples/contacts/src/main/java/sample/contact/DataSourcePopulator.java
  15. 4 3
      samples/contacts/src/main/java/sample/contact/DeleteController.java
  16. 95 0
      samples/contacts/src/main/java/sample/contact/DeletePermissionController.java
  17. 7 34
      samples/contacts/src/main/java/sample/contact/SecureIndexController.java
  18. 2 23
      samples/contacts/src/main/java/sample/contact/WebContactAddController.java
  19. 8 4
      samples/contacts/src/main/java/sample/contact/WebContactValidator.java
  20. 28 0
      samples/contacts/src/main/resources/log4j.properties
  21. 43 0
      samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml
  22. 0 142
      samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext.xml
  23. 32 45
      samples/contacts/src/main/webapp/ca/WEB-INF/web.xml
  24. 5 5
      samples/contacts/src/main/webapp/ca/login.jsp
  25. 31 129
      samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml
  26. 45 52
      samples/contacts/src/main/webapp/cas/WEB-INF/web.xml
  27. 0 1
      samples/contacts/src/main/webapp/cas/casfailed.jsp
  28. 151 0
      samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-authorization.xml
  29. 71 0
      samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-business.xml
  30. 29 0
      samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml
  31. 55 0
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/addPermission.jsp
  32. 39 0
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/adminPermission.jsp
  33. 18 0
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/deletePermission.jsp
  34. 31 17
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/hello.jsp
  35. 7 4
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp
  36. 4 5
      samples/contacts/src/main/webapp/common/secure/debug.jsp
  37. 19 118
      samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml
  38. 37 29
      samples/contacts/src/main/webapp/filter/WEB-INF/web.xml
  39. 5 5
      samples/contacts/src/main/webapp/filter/acegilogin.jsp

+ 3 - 1
changelog.txt

@@ -2,6 +2,7 @@ Changes in version 0.7 (2004-xx-xx)
 -----------------------------------
 
 * Major CVS repository restructure to support Maven and eliminate libraries
+* Major improvements to Contacts sample application (now demos ACL security)
 * Added AspectJ support (especially useful for instance-level security)
 * Added MethodDefinitionSourceAdvisor for performance and autoproxying
 * Added MethodDefinitionMap querying of interfaces defined by secure objects
@@ -11,7 +12,8 @@ Changes in version 0.7 (2004-xx-xx)
 * Improved BasicAclProvider to only respond to specified ACL object requests
 * Refactored MethodDefinitionSource to work with Method, not MethodInvocation
 * Refactored AbstractSecurityInterceptor to better support other AOP libraries
-* Fixed AbstractProcessingFitler to use removeAttribute (JRun compatibility)
+* Fixed AbstractProcessingFilter to use removeAttribute (JRun compatibility)
+* Fixed GrantedAuthorityEffectiveAclResolver support of UserDetails principals
 * Moved MethodSecurityInterceptor to ...intercept.method.aopalliance package
 * Documentation improvements
 

+ 59 - 0
samples/contacts/src/main/java/sample/contact/AddPermission.java

@@ -0,0 +1,59 @@
+/* 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 sample.contact;
+
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+
+/**
+ * Model object for add permission use case.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AddPermission {
+    //~ Instance fields ========================================================
+
+    public Contact contact;
+    public Integer permission = new Integer(SimpleAclEntry.NOTHING);
+    public String recipient;
+
+    //~ Methods ================================================================
+
+    public void setContact(Contact contact) {
+        this.contact = contact;
+    }
+
+    public Contact getContact() {
+        return contact;
+    }
+
+    public void setPermission(Integer permission) {
+        this.permission = permission;
+    }
+
+    public Integer getPermission() {
+        return permission;
+    }
+
+    public void setRecipient(String recipient) {
+        this.recipient = recipient;
+    }
+
+    public String getRecipient() {
+        return recipient;
+    }
+}

+ 162 - 0
samples/contacts/src/main/java/sample/contact/AddPermissionController.java

@@ -0,0 +1,162 @@
+/* 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 sample.contact;
+
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.dao.DataAccessException;
+
+import org.springframework.validation.BindException;
+
+import org.springframework.web.bind.RequestUtils;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.SimpleFormController;
+import org.springframework.web.servlet.view.RedirectView;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Controller for adding an ACL permission.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AddPermissionController extends SimpleFormController
+    implements InitializingBean {
+    //~ Instance fields ========================================================
+
+    private ContactManager contactManager;
+
+    //~ Methods ================================================================
+
+    public void setContactManager(ContactManager contact) {
+        this.contactManager = contact;
+    }
+
+    public ContactManager getContactManager() {
+        return contactManager;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (contactManager == null) {
+            throw new IllegalArgumentException(
+                "A ContactManager implementation is required");
+        }
+    }
+
+    protected ModelAndView disallowDuplicateFormSubmission(
+        HttpServletRequest request, HttpServletResponse response)
+        throws Exception {
+        BindException errors = new BindException(formBackingObject(request),
+                getCommandName());
+        errors.reject("err.duplicateFormSubmission",
+            "Duplicate form submission.");
+
+        return showForm(request, response, errors);
+    }
+
+    protected Object formBackingObject(HttpServletRequest request)
+        throws Exception {
+        int contactId = RequestUtils.getRequiredIntParameter(request,
+                "contactId");
+
+        Contact contact = contactManager.getById(new Integer(contactId));
+
+        AddPermission addPermission = new AddPermission();
+        addPermission.setContact(contact);
+
+        return addPermission;
+    }
+
+    protected ModelAndView handleInvalidSubmit(HttpServletRequest request,
+        HttpServletResponse response) throws Exception {
+        return disallowDuplicateFormSubmission(request, response);
+    }
+
+    protected ModelAndView onSubmit(HttpServletRequest request,
+        HttpServletResponse response, Object command, BindException errors)
+        throws Exception {
+        AddPermission addPermission = (AddPermission) command;
+
+        try {
+            contactManager.addPermission(addPermission.getContact(),
+                addPermission.getRecipient(), addPermission.getPermission());
+        } catch (DataAccessException existingPermission) {
+            existingPermission.printStackTrace();
+            errors.rejectValue("recipient", "err.recipientExistsForContact",
+                "This recipient already has permissions to this contact.");
+
+            return showForm(request, response, errors);
+        }
+
+        return new ModelAndView(new RedirectView(getSuccessView()));
+    }
+
+    protected Map referenceData(HttpServletRequest request)
+        throws Exception {
+        Map model = new HashMap();
+        model.put("recipients", listRecipients(request));
+        model.put("permissions", listPermissions(request));
+
+        return model;
+    }
+
+    private Map listPermissions(HttpServletRequest request) {
+        Map map = new LinkedHashMap();
+        map.put(new Integer(SimpleAclEntry.NOTHING),
+            getApplicationContext().getMessage("select.none", null, "None",
+                request.getLocale()));
+        map.put(new Integer(SimpleAclEntry.ADMINISTRATION),
+            getApplicationContext().getMessage("select.administer", null,
+                "Administer", request.getLocale()));
+        map.put(new Integer(SimpleAclEntry.READ),
+            getApplicationContext().getMessage("select.read", null, "Read",
+                request.getLocale()));
+        map.put(new Integer(SimpleAclEntry.DELETE),
+            getApplicationContext().getMessage("select.delete", null, "Delete",
+                request.getLocale()));
+        map.put(new Integer(SimpleAclEntry.READ_WRITE_DELETE),
+            getApplicationContext().getMessage("select.readWriteDelete", null,
+                "Read+Write+Delete", request.getLocale()));
+
+        return map;
+    }
+
+    private Map listRecipients(HttpServletRequest request) {
+        Map map = new LinkedHashMap();
+        map.put("",
+            getApplicationContext().getMessage("select.pleaseSelect", null,
+                "-- please select --", request.getLocale()));
+
+        Iterator recipientsIter = contactManager.getAllRecipients().iterator();
+
+        while (recipientsIter.hasNext()) {
+            String recipient = (String) recipientsIter.next();
+            map.put(recipient, recipient);
+        }
+
+        return map;
+    }
+}

+ 66 - 0
samples/contacts/src/main/java/sample/contact/AddPermissionValidator.java

@@ -0,0 +1,66 @@
+/* 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 sample.contact;
+
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+
+/**
+ * Validates {@link AddPermission}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AddPermissionValidator implements Validator {
+    //~ Methods ================================================================
+
+    public boolean supports(Class clazz) {
+        return clazz.equals(AddPermission.class);
+    }
+
+    public void validate(Object obj, Errors errors) {
+        AddPermission addPermission = (AddPermission) obj;
+
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "permission",
+            "err.permission", "Permission is required");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "recipient",
+            "err.recipient", "Recipient is required");
+
+        if (addPermission.getPermission() != null) {
+            int permission = addPermission.getPermission().intValue();
+
+            if ((permission != SimpleAclEntry.NOTHING)
+                && (permission != SimpleAclEntry.ADMINISTRATION)
+                && (permission != SimpleAclEntry.READ)
+                && (permission != SimpleAclEntry.DELETE)
+                && (permission != SimpleAclEntry.READ_WRITE_DELETE)) {
+                errors.rejectValue("permission", "err.permission.invalid",
+                    "The indicated permission is invalid.");
+            }
+        }
+
+        if (addPermission.getRecipient() != null) {
+            if (addPermission.getRecipient().length() > 100) {
+                errors.rejectValue("recipient", "err.recipient.length",
+                    "The recipient is too long (maximum 100 characters).");
+            }
+        }
+    }
+}

+ 92 - 0
samples/contacts/src/main/java/sample/contact/AdminPermissionController.java

@@ -0,0 +1,92 @@
+/* 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 sample.contact;
+
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.acl.AclManager;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.web.bind.RequestUtils;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.Controller;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Controller for "administer" index page.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AdminPermissionController implements Controller, InitializingBean {
+    //~ Instance fields ========================================================
+
+    private AclManager aclManager;
+    private ContactManager contactManager;
+
+    //~ Methods ================================================================
+
+    public void setAclManager(AclManager aclManager) {
+        this.aclManager = aclManager;
+    }
+
+    public AclManager getAclManager() {
+        return aclManager;
+    }
+
+    public void setContactManager(ContactManager contact) {
+        this.contactManager = contact;
+    }
+
+    public ContactManager getContactManager() {
+        return contactManager;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (contactManager == null) {
+            throw new IllegalArgumentException(
+                "A ContactManager implementation is required");
+        }
+
+        if (aclManager == null) {
+            throw new IllegalArgumentException(
+                "An aclManager implementation is required");
+        }
+    }
+
+    public ModelAndView handleRequest(HttpServletRequest request,
+        HttpServletResponse response) throws ServletException, IOException {
+        int id = RequestUtils.getRequiredIntParameter(request, "contactId");
+
+        Contact contact = contactManager.getById(new Integer(id));
+        AclEntry[] acls = aclManager.getAcls(contact);
+
+        Map model = new HashMap();
+        model.put("contact", contact);
+        model.put("acls", acls);
+
+        return new ModelAndView("adminPermission", "model", model);
+    }
+}

+ 15 - 12
samples/contacts/src/main/java/sample/contact/ClientApplication.java

@@ -25,12 +25,13 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
 
 /**
  * Demonstrates accessing the {@link ContactManager} via remoting protocols.
- *
+ * 
  * <P>
  * Based on Spring's JPetStore sample, written by Juergen Hoeller.
  * </p>
@@ -92,32 +93,34 @@ public class ClientApplication {
                 System.out.println("Found; Trying to setPassword(String) to "
                     + password);
             } catch (NoSuchMethodException ignored) {}
-             catch (IllegalAccessException ignored) {}
-             catch (InvocationTargetException ignored) {}
+            catch (IllegalAccessException ignored) {}
+            catch (InvocationTargetException ignored) {}
 
             stopWatch.start(beanName);
 
-            Contact[] contacts = null;
+            List contacts = null;
 
             for (int i = 0; i < nrOfCalls; i++) {
-                contacts = remoteContactManager.getAllByOwner(forOwner);
+                contacts = remoteContactManager.getAll();
             }
 
             stopWatch.stop();
 
-            if (contacts.length != 0) {
-                for (int i = 0; i < contacts.length; i++) {
-                    System.out.println("Contact " + i + ": "
-                        + contacts[i].toString());
+            if (contacts.size() == 0) {
+                Iterator listIterator = contacts.iterator();
+
+                while (listIterator.hasNext()) {
+                    Contact contact = (Contact) listIterator.next();
+                    System.out.println("Contact: " + contact.toString());
                 }
             } else {
-                System.out.println("No contacts found belonging to owner");
+                System.out.println(
+                    "No contacts found which this user has permission to");
             }
 
             System.out.println();
+            System.out.println(stopWatch.prettyPrint());
         }
-
-        System.out.println(stopWatch.prettyPrint());
     }
 
     public static void main(String[] args) {

+ 7 - 22
samples/contacts/src/main/java/sample/contact/Contact.java

@@ -17,10 +17,6 @@ package sample.contact;
 
 /**
  * Represents a contact.
- * 
- * <P>
- * <code>id</code> and <code>owner</code> are immutable.
- * </p>
  *
  * @author Ben Alex
  * @version $Id$
@@ -31,20 +27,15 @@ public class Contact {
     private Integer id;
     private String email;
     private String name;
-    private String owner;
 
     //~ Constructors ===========================================================
 
-    public Contact(Integer id, String name, String email, String owner) {
-        this.id = id;
+    public Contact(String name, String email) {
         this.name = name;
         this.email = email;
-        this.owner = owner;
     }
 
-    private Contact() {
-        super();
-    }
+    public Contact() {}
 
     //~ Methods ================================================================
 
@@ -66,6 +57,10 @@ public class Contact {
         return email;
     }
 
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
     /**
      * DOCUMENT ME!
      *
@@ -93,22 +88,12 @@ public class Contact {
         return name;
     }
 
-    /**
-     * DOCUMENT ME!
-     *
-     * @return Returns the owner.
-     */
-    public String getOwner() {
-        return owner;
-    }
-
     public String toString() {
         StringBuffer sb = new StringBuffer();
         sb.append(super.toString() + ": ");
         sb.append("Id: " + this.getId() + "; ");
         sb.append("Name: " + this.getName() + "; ");
-        sb.append("Email: " + this.getEmail() + "; ");
-        sb.append("Owner: " + this.getOwner());
+        sb.append("Email: " + this.getEmail());
 
         return sb.toString();
     }

+ 75 - 0
samples/contacts/src/main/java/sample/contact/ContactDao.java

@@ -0,0 +1,75 @@
+/* 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 sample.contact;
+
+import java.util.List;
+
+
+/**
+ * Provides access to the application's persistence layer.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface ContactDao {
+    //~ Methods ================================================================
+
+    public Contact getById(Integer id);
+
+    public void create(Contact contact);
+
+    /**
+     * Creates an acl_object_identity for the specified Contact.
+     *
+     * @param contact to create an entry for
+     *
+     * @return the acl_object_identity identifier
+     */
+    public Integer createAclObjectIdentity(Contact contact);
+
+    /**
+     * Given an acl_object_identitiy identifier, grant the specified recipient
+     * read access to the object identified.
+     *
+     * @param aclObjectIdentity to assign the read permission against
+     * @param recipient receiving the permission
+     * @param permission to assign
+     */
+    public void createPermission(Integer aclObjectIdentity, String recipient,
+        int permission);
+
+    public void delete(Integer contactId);
+
+    public void deletePermission(Integer aclObjectIdentity, String recipient);
+
+    public List findAll();
+
+    public List findAllPrincipals();
+
+    public List findAllRoles();
+
+    /**
+     * Obtains the acl_object_identity for the specified Contact.
+     *
+     * @param contact to locate an acl_object_identity for
+     *
+     * @return the acl_object_identity identifier or <code>null</code> if not
+     *         found
+     */
+    public Integer lookupAclObjectIdentity(Contact contact);
+
+    public void update(Contact contact);
+}

+ 309 - 0
samples/contacts/src/main/java/sample/contact/ContactDaoSpring.java

@@ -0,0 +1,309 @@
+/* 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 sample.contact;
+
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+import org.springframework.jdbc.core.SqlParameter;
+import org.springframework.jdbc.core.support.JdbcDaoSupport;
+import org.springframework.jdbc.object.MappingSqlQuery;
+import org.springframework.jdbc.object.SqlUpdate;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+
+/**
+ * Base implementation of {@link ContactDao} that uses Spring JDBC services.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ContactDaoSpring extends JdbcDaoSupport implements ContactDao {
+    //~ Instance fields ========================================================
+
+    private AclObjectIdentityByObjectIdentityQuery aclObjectIdentityByObjectIdentityQuery;
+    private AclObjectIdentityInsert aclObjectIdentityInsert;
+    private ContactDelete contactDelete;
+    private ContactInsert contactInsert;
+    private ContactUpdate contactUpdate;
+    private ContactsAllQuery contactsAllQuery;
+    private ContactsByIdQuery contactsByIdQuery;
+    private PermissionDelete permissionDelete;
+    private PermissionInsert permissionInsert;
+    private PrincipalsAllQuery principalsAllQuery;
+    private RolesAllQuery rolesAllQuery;
+
+    //~ Methods ================================================================
+
+    public Contact getById(Integer id) {
+        List list = contactsByIdQuery.execute(id.intValue());
+
+        if (list.size() == 0) {
+            return null;
+        } else {
+            return (Contact) list.get(0);
+        }
+    }
+
+    public void create(Contact contact) {
+        contactInsert.insert(contact);
+    }
+
+    public Integer createAclObjectIdentity(Contact contact) {
+        return new Integer(aclObjectIdentityInsert.insert(makeObjectIdentity(
+                    contact), null, SimpleAclEntry.class.getName()));
+    }
+
+    public void createPermission(Integer aclObjectIdentity, String recipient,
+        int permission) {
+        permissionInsert.insert(aclObjectIdentity, recipient,
+            new Integer(permission));
+    }
+
+    public void delete(Integer contactId) {
+        contactDelete.delete(contactId);
+    }
+
+    public void deletePermission(Integer aclObjectIdentity, String recipient) {
+        permissionDelete.delete(aclObjectIdentity, recipient);
+    }
+
+    public List findAll() {
+        return contactsAllQuery.execute();
+    }
+
+    public List findAllPrincipals() {
+        return principalsAllQuery.execute();
+    }
+
+    public List findAllRoles() {
+        return rolesAllQuery.execute();
+    }
+
+    public Integer lookupAclObjectIdentity(Contact contact) {
+        List list = aclObjectIdentityByObjectIdentityQuery.execute(makeObjectIdentity(
+                    contact));
+
+        if (list.size() == 0) {
+            return null;
+        } else {
+            return (Integer) list.get(0);
+        }
+    }
+
+    public void update(Contact contact) {
+        contactUpdate.update(contact);
+    }
+
+    protected void initDao() throws Exception {
+        contactInsert = new ContactInsert(getDataSource());
+        contactUpdate = new ContactUpdate(getDataSource());
+        contactDelete = new ContactDelete(getDataSource());
+        aclObjectIdentityInsert = new AclObjectIdentityInsert(getDataSource());
+        permissionInsert = new PermissionInsert(getDataSource());
+        permissionDelete = new PermissionDelete(getDataSource());
+        contactsAllQuery = new ContactsAllQuery(getDataSource());
+        principalsAllQuery = new PrincipalsAllQuery(getDataSource());
+        rolesAllQuery = new RolesAllQuery(getDataSource());
+        contactsByIdQuery = new ContactsByIdQuery(getDataSource());
+        aclObjectIdentityByObjectIdentityQuery = new AclObjectIdentityByObjectIdentityQuery(getDataSource());
+    }
+
+    private String makeObjectIdentity(Contact contact) {
+        return contact.getClass().getName() + ":" + contact.getId();
+    }
+
+    //~ Inner Classes ==========================================================
+
+    protected class AclObjectIdentityByObjectIdentityQuery
+        extends MappingSqlQuery {
+        protected AclObjectIdentityByObjectIdentityQuery(DataSource ds) {
+            super(ds,
+                "SELECT id FROM acl_object_identity WHERE object_identity = ?");
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            compile();
+        }
+
+        protected Object mapRow(ResultSet rs, int rownum)
+            throws SQLException {
+            return new Integer(rs.getInt("id"));
+        }
+    }
+
+    protected class AclObjectIdentityInsert extends SqlUpdate {
+        protected AclObjectIdentityInsert(DataSource ds) {
+            super(ds, "INSERT INTO acl_object_identity VALUES (?, ?, ?, ?)");
+            declareParameter(new SqlParameter(Types.INTEGER));
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            declareParameter(new SqlParameter(Types.INTEGER));
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            compile();
+        }
+
+        protected int insert(String objectIdentity,
+            Integer parentAclObjectIdentity, String aclClass) {
+            Object[] objs = new Object[] {null, objectIdentity, parentAclObjectIdentity, aclClass};
+            super.update(objs);
+
+            return getJdbcTemplate().queryForInt("call identity()");
+        }
+    }
+
+    protected class ContactDelete extends SqlUpdate {
+        protected ContactDelete(DataSource ds) {
+            super(ds, "DELETE FROM contacts WHERE id = ?");
+            declareParameter(new SqlParameter(Types.INTEGER));
+            compile();
+        }
+
+        protected void delete(Integer contactId) {
+            super.update(contactId.intValue());
+        }
+    }
+
+    protected class ContactInsert extends SqlUpdate {
+        protected ContactInsert(DataSource ds) {
+            super(ds, "INSERT INTO contacts VALUES (?, ?, ?)");
+            declareParameter(new SqlParameter(Types.INTEGER));
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            compile();
+        }
+
+        protected void insert(Contact contact) {
+            Object[] objs = new Object[] {contact.getId(), contact.getName(), contact
+                    .getEmail()};
+            super.update(objs);
+        }
+    }
+
+    protected class ContactUpdate extends SqlUpdate {
+        protected ContactUpdate(DataSource ds) {
+            super(ds,
+                "UPDATE contacts SET contact_name = ?, address = ? WHERE id = ?");
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            declareParameter(new SqlParameter(Types.INTEGER));
+            compile();
+        }
+
+        protected void update(Contact contact) {
+            Object[] objs = new Object[] {contact.getName(), contact.getEmail(), contact
+                    .getId()};
+            super.update(objs);
+        }
+    }
+
+    protected class ContactsAllQuery extends MappingSqlQuery {
+        protected ContactsAllQuery(DataSource ds) {
+            super(ds, "SELECT id, contact_name, email FROM contacts ORDER BY id");
+            compile();
+        }
+
+        protected Object mapRow(ResultSet rs, int rownum)
+            throws SQLException {
+            Contact contact = new Contact();
+            contact.setId(new Integer(rs.getInt("id")));
+            contact.setName(rs.getString("contact_name"));
+            contact.setEmail(rs.getString("email"));
+
+            return contact;
+        }
+    }
+
+    protected class ContactsByIdQuery extends MappingSqlQuery {
+        protected ContactsByIdQuery(DataSource ds) {
+            super(ds,
+                "SELECT id, contact_name, email FROM contacts WHERE id = ? ORDER BY id");
+            declareParameter(new SqlParameter(Types.INTEGER));
+            compile();
+        }
+
+        protected Object mapRow(ResultSet rs, int rownum)
+            throws SQLException {
+            Contact contact = new Contact();
+            contact.setId(new Integer(rs.getInt("id")));
+            contact.setName(rs.getString("contact_name"));
+            contact.setEmail(rs.getString("email"));
+
+            return contact;
+        }
+    }
+
+    protected class PermissionDelete extends SqlUpdate {
+        protected PermissionDelete(DataSource ds) {
+            super(ds,
+                "DELETE FROM acl_permission WHERE ACL_OBJECT_IDENTITY = ? AND RECIPIENT = ?");
+            declareParameter(new SqlParameter(Types.INTEGER));
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            compile();
+        }
+
+        protected void delete(Integer aclObjectIdentity, String recipient) {
+            super.update(new Object[] {aclObjectIdentity, recipient});
+        }
+    }
+
+    protected class PermissionInsert extends SqlUpdate {
+        protected PermissionInsert(DataSource ds) {
+            super(ds, "INSERT INTO acl_permission VALUES (?, ?, ?, ?);");
+            declareParameter(new SqlParameter(Types.INTEGER));
+            declareParameter(new SqlParameter(Types.INTEGER));
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            declareParameter(new SqlParameter(Types.INTEGER));
+            compile();
+        }
+
+        protected int insert(Integer aclObjectIdentity, String recipient,
+            Integer mask) {
+            Object[] objs = new Object[] {null, aclObjectIdentity, recipient, mask};
+            super.update(objs);
+
+            return getJdbcTemplate().queryForInt("call identity()");
+        }
+    }
+
+    protected class PrincipalsAllQuery extends MappingSqlQuery {
+        protected PrincipalsAllQuery(DataSource ds) {
+            super(ds, "SELECT username FROM users ORDER BY username");
+            compile();
+        }
+
+        protected Object mapRow(ResultSet rs, int rownum)
+            throws SQLException {
+            return rs.getString("username");
+        }
+    }
+
+    protected class RolesAllQuery extends MappingSqlQuery {
+        protected RolesAllQuery(DataSource ds) {
+            super(ds,
+                "SELECT DISTINCT authority FROM authorities ORDER BY authority");
+            compile();
+        }
+
+        protected Object mapRow(ResultSet rs, int rownum)
+            throws SQLException {
+            return rs.getString("authority");
+        }
+    }
+}

+ 13 - 5
samples/contacts/src/main/java/sample/contact/ContactManager.java

@@ -15,8 +15,11 @@
 
 package sample.contact;
 
+import java.util.List;
+
+
 /**
- * Iterface for the application's business object.
+ * Interface for the application's services layer.
  *
  * @author Ben Alex
  * @version $Id$
@@ -24,15 +27,20 @@ package sample.contact;
 public interface ContactManager {
     //~ Methods ================================================================
 
-    public Contact[] getAllByOwner(String owner);
+    public List getAll();
 
-    public Contact getById(Integer id);
+    public List getAllRecipients();
 
-    public Integer getNextId();
+    public Contact getById(Integer id);
 
     public Contact getRandomContact();
 
+    public void addPermission(Contact contact, String recipient,
+        Integer permission);
+
+    public void create(Contact contact);
+
     public void delete(Contact contact);
 
-    public void save(Contact contact);
+    public void deletePermission(Contact contact, String recipient);
 }

+ 60 - 114
samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java

@@ -15,159 +15,105 @@
 
 package sample.contact;
 
-import java.util.HashMap;
-import java.util.Iterator;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+import net.sf.acegisecurity.context.ContextHolder;
+import net.sf.acegisecurity.context.SecureContext;
+
+import org.springframework.beans.factory.InitializingBean;
+
 import java.util.List;
-import java.util.Map;
 import java.util.Random;
-import java.util.Vector;
 
 
 /**
- * Backend business object that manages the contacts.
- * 
- * <P>
- * As a backend, it never faces the public callers. It is always accessed via
- * the {@link ContactManagerFacade}.
- * </p>
- * 
- * <P>
- * This facade approach is not really necessary in this application, and is
- * done simply to demonstrate granting additional authorities via the
- * <code>RunAsManager</code>.
- * </p>
+ * Concrete implementation of {@link ContactManager}.
  *
  * @author Ben Alex
  * @version $Id$
  */
-public class ContactManagerBackend implements ContactManager {
+public class ContactManagerBackend implements ContactManager, InitializingBean {
     //~ Instance fields ========================================================
 
-    private Map contacts;
-
-    //~ Constructors ===========================================================
-
-    public ContactManagerBackend() {
-        this.contacts = new HashMap();
-        save(new Contact(this.getNextId(), "John Smith", "john@somewhere.com",
-                "marissa"));
-        save(new Contact(this.getNextId(), "Michael Citizen",
-                "michael@xyz.com", "marissa"));
-        save(new Contact(this.getNextId(), "Joe Bloggs", "joe@demo.com",
-                "marissa"));
-        save(new Contact(this.getNextId(), "Karen Sutherland",
-                "karen@sutherland.com", "dianne"));
-        save(new Contact(this.getNextId(), "Mitchell Howard",
-                "mitchell@abcdef.com", "dianne"));
-        save(new Contact(this.getNextId(), "Rose Costas", "rose@xyz.com",
-                "scott"));
-        save(new Contact(this.getNextId(), "Amanda Smith", "amanda@abcdef.com",
-                "scott"));
-    }
+    private ContactDao contactDao;
+    private int counter = 100;
 
     //~ Methods ================================================================
 
-    /**
-     * Security system expects ROLE_RUN_AS_SERVER
-     *
-     * @param owner DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     */
-    public Contact[] getAllByOwner(String owner) {
-        List list = new Vector();
-        Iterator iter = this.contacts.keySet().iterator();
-
-        while (iter.hasNext()) {
-            Integer contactId = (Integer) iter.next();
-            Contact contact = (Contact) this.contacts.get(contactId);
+    public List getAll() {
+        return contactDao.findAll();
+    }
 
-            if (contact.getOwner().equals(owner)) {
-                list.add(contact);
-            }
-        }
+    public List getAllRecipients() {
+        List list = contactDao.findAllPrincipals();
+        list.addAll(contactDao.findAllRoles());
 
-        if (list.size() == 0) {
-            return null;
-        } else {
-            return (Contact[]) list.toArray(new Contact[list.size()]);
-        }
+        return list;
     }
 
-    /**
-     * Security system expects ROLE_RUN_AS_SERVER
-     *
-     * @param id DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     */
     public Contact getById(Integer id) {
-        return (Contact) this.contacts.get(id);
+        return contactDao.getById(id);
     }
 
-    /**
-     * Public method
-     *
-     * @return DOCUMENT ME!
-     */
-    public Integer getNextId() {
-        int max = 0;
-        Iterator iter = this.contacts.keySet().iterator();
-
-        while (iter.hasNext()) {
-            Integer id = (Integer) iter.next();
-
-            if (id.intValue() > max) {
-                max = id.intValue();
-            }
-        }
+    public void setContactDao(ContactDao contactDao) {
+        this.contactDao = contactDao;
+    }
 
-        return new Integer(max + 1);
+    public ContactDao getContactDao() {
+        return contactDao;
     }
 
     /**
-     * This is a public method, meaning a client could call this method
-     * directly (ie not via a facade). If this was an issue, the public method
-     * on the facade should not be public but secure. Quite possibly an
-     * AnonymousAuthenticationToken and associated provider could be used on a
-     * secure method, thus allowing a RunAsManager to protect the backend.
+     * This is a public method.
      *
      * @return DOCUMENT ME!
      */
     public Contact getRandomContact() {
         Random rnd = new Random();
-        int getNumber = rnd.nextInt(this.contacts.size()) + 1;
-        Iterator iter = this.contacts.keySet().iterator();
-        int i = 0;
+        List contacts = contactDao.findAll();
+        int getNumber = rnd.nextInt(contacts.size());
 
-        while (iter.hasNext()) {
-            i++;
+        return (Contact) contacts.get(getNumber);
+    }
 
-            Integer id = (Integer) iter.next();
+    public void addPermission(Contact contact, String recipient,
+        Integer permission) {
+        Integer aclObjectIdentity = contactDao.lookupAclObjectIdentity(contact);
+        contactDao.createPermission(aclObjectIdentity, recipient,
+            permission.intValue());
+    }
 
-            if (i == getNumber) {
-                return (Contact) this.contacts.get(id);
-            }
+    public void afterPropertiesSet() throws Exception {
+        if (contactDao == null) {
+            throw new IllegalArgumentException("contactDao required");
         }
+    }
 
-        return null;
+    public void create(Contact contact) {
+        // Create the Contact itself
+        contact.setId(new Integer(counter++));
+        contactDao.create(contact);
+
+        // Grant the current principal access to the contact 
+        Integer aclObjectIdentity = contactDao.createAclObjectIdentity(contact);
+        contactDao.createPermission(aclObjectIdentity, getUsername(),
+            SimpleAclEntry.ADMINISTRATION);
     }
 
-    /**
-     * Security system expects ROLE_RUN_AS_SERVER
-     *
-     * @param contact DOCUMENT ME!
-     */
     public void delete(Contact contact) {
-        this.contacts.remove(contact.getId());
+        contactDao.delete(contact.getId());
     }
 
-    /**
-     * Security system expects ROLE_RUN_AS_SERVER
-     *
-     * @param contact DOCUMENT ME!
-     */
-    public void save(Contact contact) {
-        this.contacts.put(contact.getId(), contact);
+    public void deletePermission(Contact contact, String recipient) {
+        Integer aclObjectIdentity = contactDao.lookupAclObjectIdentity(contact);
+        contactDao.deletePermission(aclObjectIdentity, recipient);
+    }
+
+    public void update(Contact contact) {
+        contactDao.update(contact);
+    }
+
+    protected String getUsername() {
+        return ((SecureContext) ContextHolder.getContext()).getAuthentication()
+                .getPrincipal().toString();
     }
 }

+ 0 - 149
samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java

@@ -1,149 +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 sample.contact;
-
-import net.sf.acegisecurity.AccessDeniedException;
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.context.ContextHolder;
-import net.sf.acegisecurity.context.SecureContext;
-
-import org.springframework.beans.factory.InitializingBean;
-
-
-/**
- * This is the public facade to the application's main business object.
- * 
- * <p>
- * Used to demonstrate security configuration in a multi-tier application. Most
- * methods of this class are secured via standard security definitions in the
- * bean context. There is one method that supplements these security checks.
- * All methods delegate to a "backend" object. The "backend" object relies on
- * the facade's <code>RunAsManager</code> assigning an additional
- * <code>GrantedAuthority</code> that is required to call its methods.
- * </p>
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class ContactManagerFacade implements ContactManager, InitializingBean {
-    //~ Instance fields ========================================================
-
-    private ContactManager backend;
-
-    //~ Methods ================================================================
-
-    /**
-     * Security system will ensure the owner parameter equals the currently
-     * logged in user.
-     *
-     * @param owner DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     */
-    public Contact[] getAllByOwner(String owner) {
-        return backend.getAllByOwner(owner);
-    }
-
-    public void setBackend(ContactManager backend) {
-        this.backend = backend;
-    }
-
-    public ContactManager getBackend() {
-        return backend;
-    }
-
-    /**
-     * Security system will ensure logged in user has ROLE_TELLER.
-     * 
-     * <p>
-     * Security system cannot ensure that only the owner can get the contact,
-     * as doing so would require it to specifically open the contact. Whilst
-     * possible, this would be expensive as the operation would be performed
-     * both by the security system as well as the implementation. Instead the
-     * facade will confirm the contact.getOwner() matches what is on the
-     * ContextHolder.
-     * </p>
-     *
-     * @param id DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     *
-     * @throws AccessDeniedException DOCUMENT ME!
-     */
-    public Contact getById(Integer id) {
-        Contact result = backend.getById(id);
-        Authentication auth = ((SecureContext) ContextHolder.getContext())
-            .getAuthentication();
-
-        String username = auth.getPrincipal().toString();
-
-        if (auth.getPrincipal() instanceof UserDetails) {
-            username = ((UserDetails) auth.getPrincipal()).getUsername();
-        }
-
-        if (username.equals(result.getOwner())) {
-            return result;
-        } else {
-            throw new AccessDeniedException(
-                "The requested id is not owned by the currently logged in user");
-        }
-    }
-
-    /**
-     * Public method.
-     *
-     * @return DOCUMENT ME!
-     */
-    public Integer getNextId() {
-        return backend.getNextId();
-    }
-
-    /**
-     * Public method.
-     *
-     * @return DOCUMENT ME!
-     */
-    public Contact getRandomContact() {
-        return backend.getRandomContact();
-    }
-
-    public void afterPropertiesSet() throws Exception {
-        if (backend == null) {
-            throw new IllegalArgumentException(
-                "A backend ContactManager implementation is required");
-        }
-    }
-
-    /**
-     * Security system will ensure logged in user has ROLE_SUPERVISOR.
-     *
-     * @param contact DOCUMENT ME!
-     */
-    public void delete(Contact contact) {
-        backend.delete(contact);
-    }
-
-    /**
-     * Security system will ensure the owner specified via contact.getOwner()
-     * equals the currently logged in user.
-     *
-     * @param contact DOCUMENT ME!
-     */
-    public void save(Contact contact) {
-        backend.save(contact);
-    }
-}

+ 0 - 117
samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java

@@ -1,117 +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 sample.contact;
-
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.ConfigAttribute;
-import net.sf.acegisecurity.ConfigAttributeDefinition;
-import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.vote.AccessDecisionVoter;
-
-import org.aopalliance.intercept.MethodInvocation;
-
-import java.util.Iterator;
-
-
-/**
- * Implementation of an {@link AccessDecisionVoter} that provides
- * application-specific security for the Contact application.
- * 
- * <p>
- * If the {@link ConfigAttribute#getAttribute()} has a value of
- * <code>CONTACT_OWNED_BY_CURRENT_USER</code>, the String or the
- * Contact.getOwner() associated with the method call is compared with the
- * Authentication.getPrincipal().toString() result. If it matches, the voter
- * votes to grant access. If they do not match, it votes to deny access.
- * </p>
- * 
- * <p>
- * All comparisons are case sensitive.
- * </p>
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class ContactSecurityVoter implements AccessDecisionVoter {
-    //~ Methods ================================================================
-
-    public boolean supports(ConfigAttribute attribute) {
-        if ("CONTACT_OWNED_BY_CURRENT_USER".equals(attribute.getAttribute())) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public boolean supports(Class clazz) {
-        if (MethodInvocation.class.isAssignableFrom(clazz)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public int vote(Authentication authentication, Object object,
-        ConfigAttributeDefinition config) {
-        if ((object == null) || !this.supports(object.getClass())) {
-            throw new IllegalArgumentException(
-                "Does not support the presented Object type");
-        }
-
-        MethodInvocation invocation = (MethodInvocation) object;
-
-        int result = ACCESS_ABSTAIN;
-        Iterator iter = config.getConfigAttributes();
-
-        while (iter.hasNext()) {
-            ConfigAttribute attribute = (ConfigAttribute) iter.next();
-
-            if (this.supports(attribute)) {
-                result = ACCESS_DENIED;
-
-                // Lookup the account number being passed
-                String passedOwner = null;
-
-                for (int i = 0; i < invocation.getArguments().length; i++) {
-                    Class argClass = invocation.getArguments()[i].getClass();
-
-                    if (String.class.isAssignableFrom(argClass)) {
-                        passedOwner = (String) invocation.getArguments()[i];
-                    } else if (Contact.class.isAssignableFrom(argClass)) {
-                        passedOwner = ((Contact) invocation.getArguments()[i])
-                            .getOwner();
-                    }
-                }
-
-                if (passedOwner != null) {
-                    String username = authentication.getPrincipal().toString();
-
-                    if (authentication.getPrincipal() instanceof UserDetails) {
-                        username = ((UserDetails) authentication.getPrincipal())
-                            .getUsername();
-                    }
-
-                    // Check the authentication principal matches the passed owner
-                    if (passedOwner.equals(username)) {
-                        return ACCESS_GRANTED;
-                    }
-                }
-            }
-        }
-
-        return result;
-    }
-}

+ 153 - 0
samples/contacts/src/main/java/sample/contact/DataSourcePopulator.java

@@ -0,0 +1,153 @@
+/* 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 sample.contact;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+
+
+/**
+ * Populates the Contacts in-memory database with contact and ACL information.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class DataSourcePopulator implements InitializingBean {
+    //~ Instance fields ========================================================
+
+    private DataSource dataSource;
+
+    //~ Methods ================================================================
+
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (dataSource == null) {
+            throw new IllegalArgumentException("dataSource required");
+        }
+
+        JdbcTemplate template = new JdbcTemplate(dataSource);
+
+        template.execute(
+            "CREATE TABLE CONTACTS(ID INTEGER NOT NULL PRIMARY KEY, CONTACT_NAME VARCHAR_IGNORECASE(50) NOT NULL, EMAIL VARCHAR_IGNORECASE(50) NOT NULL)");
+        template.execute(
+            "INSERT INTO contacts VALUES (1, 'John Smith', 'john@somewhere.com');"); // marissa
+        template.execute(
+            "INSERT INTO contacts VALUES (2, 'Michael Citizen', 'michael@xyz.com');"); // marissa
+        template.execute(
+            "INSERT INTO contacts VALUES (3, 'Joe Bloggs', 'joe@demo.com');"); // marissa
+        template.execute(
+            "INSERT INTO contacts VALUES (4, 'Karen Sutherland', 'karen@sutherland.com');"); // marissa + dianne + scott
+        template.execute(
+            "INSERT INTO contacts VALUES (5, 'Mitchell Howard', 'mitchell@abcdef.com');"); // dianne
+        template.execute(
+            "INSERT INTO contacts VALUES (6, 'Rose Costas', 'rose@xyz.com');"); // dianne + scott
+        template.execute(
+            "INSERT INTO contacts VALUES (7, 'Amanda Smith', 'amanda@abcdef.com');"); // scott
+        template.execute(
+            "INSERT INTO contacts VALUES (8, 'Cindy Smith', 'cindy@smith.com');"); // dianne + scott
+        template.execute(
+            "INSERT INTO contacts VALUES (9, 'Jonathan Citizen', 'jonathan@xyz.com');"); // scott
+        template.execute(
+            "CREATE TABLE ACL_OBJECT_IDENTITY(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 100)  NOT NULL PRIMARY KEY,OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,PARENT_OBJECT INTEGER,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT UNIQUE_OBJECT_IDENTITY UNIQUE(OBJECT_IDENTITY),CONSTRAINT SYS_FK_3 FOREIGN KEY(PARENT_OBJECT) REFERENCES ACL_OBJECT_IDENTITY(ID))");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (1, 'sample.contact.Contact:1', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (2, 'sample.contact.Contact:2', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (3, 'sample.contact.Contact:3', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (4, 'sample.contact.Contact:4', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (5, 'sample.contact.Contact:5', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (6, 'sample.contact.Contact:6', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (7, 'sample.contact.Contact:7', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (8, 'sample.contact.Contact:8', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (9, 'sample.contact.Contact:9', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "CREATE TABLE ACL_PERMISSION(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 100)  NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY INTEGER NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,MASK INTEGER NOT NULL,CONSTRAINT UNIQUE_RECIPIENT UNIQUE(ACL_OBJECT_IDENTITY,RECIPIENT),CONSTRAINT SYS_FK_7 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID))");
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 1, 'marissa', 1);"); // administer
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2);"); // read
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 3, 'marissa', 22);"); // read+write+delete 
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 4, 'marissa', 1);"); // administer
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 4, 'dianne', 1);"); // administer
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 4, 'scott', 2);"); // read 
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 5, 'dianne', 2);"); // read
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 6, 'dianne', 22);"); // read+write+delete
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 6, 'scott', 2);"); // read
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 7, 'scott', 1);"); // administer
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 8, 'dianne', 2);"); // read
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 8, 'scott', 2);"); // read
+        template.execute(
+            "INSERT INTO acl_permission VALUES (null, 9, 'scott', 22);"); // read+write+delete
+        template.execute(
+            "CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,ENABLED BOOLEAN NOT NULL);");
+        template.execute(
+            "CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME));");
+        template.execute(
+            "CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY);");
+
+        /*
+                   Passwords encoded using MD5, NOT in Base64 format, with null as salt
+                   Encoded password for marissa is "koala"
+                   Encoded password for dianne is "emu"
+                   Encoded password for scott is "wombat"
+                   Encoded password for peter is "opal" (but user is disabled)
+        
+         */
+        template.execute(
+            "INSERT INTO USERS VALUES('marissa','a564de63c2d0da68cf47586ee05984d7',TRUE);");
+        template.execute(
+            "INSERT INTO USERS VALUES('dianne','65d15fe9156f9c4bbffd98085992a44e',TRUE);");
+        template.execute(
+            "INSERT INTO USERS VALUES('scott','2b58af6dddbd072ed27ffc86725d7d3a',TRUE);");
+        template.execute(
+            "INSERT INTO USERS VALUES('peter','22b5c9accc6e1ba628cedc63a72d57f8',FALSE);");
+        template.execute(
+            "INSERT INTO AUTHORITIES VALUES('marissa','ROLE_USER');");
+        template.execute(
+            "INSERT INTO AUTHORITIES VALUES('marissa','ROLE_SUPERVISOR');");
+        template.execute(
+            "INSERT INTO AUTHORITIES VALUES('dianne','ROLE_USER');");
+        template.execute("INSERT INTO AUTHORITIES VALUES('scott','ROLE_USER');");
+        template.execute("INSERT INTO AUTHORITIES VALUES('peter','ROLE_USER');");
+    }
+}

+ 4 - 3
samples/contacts/src/main/java/sample/contact/DeleteController.java

@@ -17,6 +17,7 @@ package sample.contact;
 
 import org.springframework.beans.factory.InitializingBean;
 
+import org.springframework.web.bind.RequestUtils;
 import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.mvc.Controller;
 
@@ -28,7 +29,7 @@ import javax.servlet.http.HttpServletResponse;
 
 
 /**
- * Controller to delete a contact page.
+ * Controller to delete a contact.
  *
  * @author Ben Alex
  * @version $Id$
@@ -57,8 +58,8 @@ public class DeleteController implements Controller, InitializingBean {
 
     public ModelAndView handleRequest(HttpServletRequest request,
         HttpServletResponse response) throws ServletException, IOException {
-        Integer id = new Integer(request.getParameter("id"));
-        Contact contact = contactManager.getById(id);
+        int id = RequestUtils.getRequiredIntParameter(request, "contactId");
+        Contact contact = contactManager.getById(new Integer(id));
         contactManager.delete(contact);
 
         return new ModelAndView("deleted", "contact", contact);

+ 95 - 0
samples/contacts/src/main/java/sample/contact/DeletePermissionController.java

@@ -0,0 +1,95 @@
+/* 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 sample.contact;
+
+import net.sf.acegisecurity.acl.AclManager;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.web.bind.RequestUtils;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.Controller;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Controller for deleting an ACL permission.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class DeletePermissionController implements Controller, InitializingBean {
+    //~ Instance fields ========================================================
+
+    private AclManager aclManager;
+    private ContactManager contactManager;
+
+    //~ Methods ================================================================
+
+    public void setAclManager(AclManager aclManager) {
+        this.aclManager = aclManager;
+    }
+
+    public AclManager getAclManager() {
+        return aclManager;
+    }
+
+    public void setContactManager(ContactManager contact) {
+        this.contactManager = contact;
+    }
+
+    public ContactManager getContactManager() {
+        return contactManager;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (contactManager == null) {
+            throw new IllegalArgumentException(
+                "A ContactManager implementation is required");
+        }
+
+        if (aclManager == null) {
+            throw new IllegalArgumentException(
+                "An aclManager implementation is required");
+        }
+    }
+
+    public ModelAndView handleRequest(HttpServletRequest request,
+        HttpServletResponse response) throws ServletException, IOException {
+        int contactId = RequestUtils.getRequiredIntParameter(request,
+                "contactId");
+        String recipient = RequestUtils.getRequiredStringParameter(request,
+                "recipient");
+
+        Contact contact = contactManager.getById(new Integer(contactId));
+
+        contactManager.deletePermission(contact, recipient);
+
+        Map model = new HashMap();
+        model.put("contact", contact);
+        model.put("recipient", recipient);
+
+        return new ModelAndView("deletePermission", "model", model);
+    }
+}

+ 7 - 34
samples/contacts/src/main/java/sample/contact/SecureIndexController.java

@@ -15,13 +15,6 @@
 
 package sample.contact;
 
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
-import net.sf.acegisecurity.GrantedAuthority;
-import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.context.ContextHolder;
-import net.sf.acegisecurity.context.SecureContext;
-
 import org.springframework.beans.factory.InitializingBean;
 
 import org.springframework.web.servlet.ModelAndView;
@@ -30,6 +23,7 @@ import org.springframework.web.servlet.mvc.Controller;
 import java.io.IOException;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.servlet.ServletException;
@@ -67,38 +61,17 @@ public class SecureIndexController implements Controller, InitializingBean {
 
     public ModelAndView handleRequest(HttpServletRequest request,
         HttpServletResponse response) throws ServletException, IOException {
-        SecureContext secureContext = ((SecureContext) ContextHolder.getContext());
-
-        if (null == secureContext) {
-            throw new AuthenticationCredentialsNotFoundException(
-                "Authentication credentials were not found in the "
-                + "SecureContext");
-        }
-
-        // Lookup username. As we must accommodate DaoAuthenticationProvider,
-        // CAS and container based authentication, we take care with casting
-        Authentication auth = secureContext.getAuthentication();
-        String username = auth.getPrincipal().toString();
+        List myContactsList = contactManager.getAll();
+        Contact[] myContacts;
 
-        if (auth.getPrincipal() instanceof UserDetails) {
-            username = ((UserDetails) auth.getPrincipal()).getUsername();
+        if (myContactsList.size() == 0) {
+            myContacts = null;
+        } else {
+            myContacts = (Contact[]) myContactsList.toArray(new Contact[] {});
         }
 
-        boolean supervisor = false;
-        GrantedAuthority[] granted = auth.getAuthorities();
-
-        for (int i = 0; i < granted.length; i++) {
-            if (granted[i].getAuthority().equals("ROLE_SUPERVISOR")) {
-                supervisor = true;
-            }
-        }
-
-        Contact[] myContacts = contactManager.getAllByOwner(username);
-
         Map model = new HashMap();
         model.put("contacts", myContacts);
-        model.put("supervisor", new Boolean(supervisor));
-        model.put("user", username);
 
         return new ModelAndView("index", "model", model);
     }

+ 2 - 23
samples/contacts/src/main/java/sample/contact/WebContactAddController.java

@@ -15,19 +15,10 @@
 
 package sample.contact;
 
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.context.ContextHolder;
-import net.sf.acegisecurity.context.SecureContext;
-
 import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.mvc.SimpleFormController;
 import org.springframework.web.servlet.view.RedirectView;
 
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 
@@ -57,20 +48,8 @@ public class WebContactAddController extends SimpleFormController {
         String name = ((WebContact) command).getName();
         String email = ((WebContact) command).getEmail();
 
-        Authentication auth = ((SecureContext) ContextHolder.getContext())
-            .getAuthentication();
-        String owner = auth.getPrincipal().toString();
-
-        if (auth.getPrincipal() instanceof UserDetails) {
-            owner = ((UserDetails) auth.getPrincipal()).getUsername();
-        }
-
-        Contact contact = new Contact(contactManager.getNextId(), name, email,
-                owner);
-        contactManager.save(contact);
-
-        Map myModel = new HashMap();
-        myModel.put("now", new Date());
+        Contact contact = new Contact(name, email);
+        contactManager.create(contact);
 
         return new ModelAndView(new RedirectView(getSuccessView()));
     }

+ 8 - 4
samples/contacts/src/main/java/sample/contact/WebContactValidator.java

@@ -35,12 +35,16 @@ public class WebContactValidator implements Validator {
     public void validate(Object obj, Errors errors) {
         WebContact wc = (WebContact) obj;
 
-        if ((wc.getName() == null) || (wc.getName().length() < 3)) {
-            errors.rejectValue("name", "not-used", null, "Name is required.");
+        if ((wc.getName() == null) || (wc.getName().length() < 3)
+            || (wc.getName().length() > 50)) {
+            errors.rejectValue("name", "err.name",
+                "Name 3-50 characters is required.");
         }
 
-        if ((wc.getEmail() == null) || (wc.getEmail().length() < 3)) {
-            errors.rejectValue("email", "not-used", null, "Email is required.");
+        if ((wc.getEmail() == null) || (wc.getEmail().length() < 3)
+            || (wc.getEmail().length() > 50)) {
+            errors.rejectValue("email", "err.email",
+                "Email 3-50 characters is required.");
         }
     }
 }

+ 28 - 0
samples/contacts/src/main/resources/log4j.properties

@@ -0,0 +1,28 @@
+# Global logging configuration
+log4j.rootLogger=WARN, stdout, fileout
+
+#log4j.logger.org.springframework.aop.framework.autoproxy=DEBUG, stdout, fileout
+#log4j.logger.org.springframework.aop.framework.autoproxy.metadata=DEBUG, stdout, fileout
+#log4j.logger.org.springframework.aop.framework.autoproxy.target=DEBUG, stdout, fileout
+#log4j.logger.org.springframework.transaction.interceptor=DEBUG, stdout, fileout
+#log4j.logger.net.sf.acegisecurity.intercept=DEBUG, stdout, fileout
+#log4j.logger.net.sf.acegisecurity.intercept.method=DEBUG, stdout, fileout
+#log4j.logger.net.sf.acegisecurity.afterinvocation=DEBUG, stdout, fileout
+#log4j.logger.net.sf.acegisecurity.acl=DEBUG, stdout, fileout
+#log4j.logger.net.sf.acegisecurity.acl.basic=DEBUG, stdout, fileout
+#log4j.logger.net.sf.acegisecurity.taglibs.authz=DEBUG, stdout, fileout
+
+
+# Console output...
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.conversionPattern=[%p,%c{1},%t] %m%n
+
+# Rolling log file output...
+log4j.appender.fileout=org.apache.log4j.RollingFileAppender
+log4j.appender.fileout.File=log4j.log
+#log4j.appender.fileout.File=${webapp.root}/WEB-INF/log4j.log
+log4j.appender.fileout.MaxFileSize=100KB
+log4j.appender.fileout.MaxBackupIndex=1
+log4j.appender.fileout.layout=org.apache.log4j.PatternLayout
+log4j.appender.fileout.layout.conversionPattern=%d{ABSOLUTE} %5p %c{1},%t:%L - %m%n

+ 43 - 0
samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!--
+  - Application context containing authentication, channel
+  - security and web URI beans.
+  -
+  - Only used by "ca" artifact.
+  -
+  - $Id$
+  -->
+
+<beans>
+
+   <!-- ======================== AUTHENTICATION ======================= -->
+
+	<!-- We could also have a daoAuthenticationProvider in order to
+		 process BASIC authentication requests, but this has been
+		 left out given container adapters aren't recommended and
+		 it would increase the number of bean declarations required -->
+   <bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
+      <property name="providers">
+         <list>
+		    <ref local="authByAdapterProvider"/>
+         </list>
+      </property>
+   </bean>
+
+   <bean id="authByAdapterProvider" class="net.sf.acegisecurity.adapters.AuthByAdapterProvider">
+  		<property name="key"><value>my_password</value></property>
+   </bean>
+
+   <bean id="httpRequestIntegrationFilter" class="net.sf.acegisecurity.adapters.HttpRequestIntegrationFilter"/>
+
+	<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
+	
+				<!-- Implement by servlet specification -->
+
+	<!-- ===================== HTTP REQUEST SECURITY ==================== -->
+
+				<!-- Implement by servlet specification -->
+
+</beans>

+ 0 - 142
samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext.xml

@@ -1,142 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
-
-<!--
-  - Application context loaded by ContextLoaderListener if using container adapters
-  - $Id$
-  -->
-
-<beans>
-
-	<!-- =================== SECURITY SYSTEM DEFINITIONS ================== -->
-	
-	<!-- RunAsManager -->
-	<bean id="runAsManager" class="net.sf.acegisecurity.runas.RunAsManagerImpl">
-     	<property name="key"><value>my_run_as_password</value></property>
- 	</bean>
-
-	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHENTICATION DEFINITIONS ~~~~~~~~~~~~~~~~~~ -->
-	
-	<bean id="runAsAuthenticationProvider" class="net.sf.acegisecurity.runas.RunAsImplAuthenticationProvider">
-     	<property name="key"><value>my_run_as_password</value></property>
- 	</bean>
-
-	<bean id="authByAdapterProvider" class="net.sf.acegisecurity.adapters.AuthByAdapterProvider">
-  		<property name="key"><value>my_password</value></property>
- 	</bean>
-
-	<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
-		<property name="providers">
-		  <list>
-		    <ref local="runAsAuthenticationProvider"/>
-		    <ref local="authByAdapterProvider"/>
-		    <ref local="daoAuthenticationProvider"/>
-		  </list>
-		</property>
-	</bean>
-
-	<bean id="inMemoryDaoImpl" class="net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl">
-  		<property name="userMap">
-			<value>
-				marissa=koala,ROLE_TELLER,ROLE_SUPERVISOR
-				dianne=emu,ROLE_TELLER
-				scott=wombat,ROLE_TELLER
-				peter=opal,disabled,ROLE_TELLER
-			</value>
-		</property>
-	</bean>
-	
-	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
-     	<property name="authenticationDao"><ref local="inMemoryDaoImpl"/></property>
-	</bean>
-
-	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
-		<property name="authenticationManager"><ref local="authenticationManager"/></property>
-		<property name="authenticationEntryPoint"><ref local="basicProcessingFilterEntryPoint"/></property>
-	</bean>
-
-	<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
-		<property name="realmName"><value>Contacts Realm</value></property>
-	</bean>
-
-	<bean id="autoIntegrationFilter" class="net.sf.acegisecurity.ui.AutoIntegrationFilter" />
-
-	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
-
-	<!-- An access decision voter that reads ROLE_* configuaration settings -->
-	<bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
-
-	<!-- An access decision voter that reads CONTACT_OWNED_BY_CURRENT_USER configuaration settings -->
-	<bean id="contactSecurityVoter" class="sample.contact.ContactSecurityVoter"/>
-
-	<!-- An access decision manager used by the business objects -->
-	<bean id="businessAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
-   		<property name="allowIfAllAbstainDecisions"><value>false</value></property>
-		<property name="decisionVoters">
-		  <list>
-		    <ref local="roleVoter"/>
-		    <ref local="contactSecurityVoter"/>
-		  </list>
-		</property>
-	</bean>
-
-	<!-- ===================== SECURITY DEFINITIONS ======================= -->
-	
-	<bean id="publicContactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
-    	<property name="authenticationManager"><ref local="authenticationManager"/></property>
-    	<property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
-    	<property name="runAsManager"><ref local="runAsManager"/></property>
- 		<property name="objectDefinitionSource">
-			<value>
-				sample.contact.ContactManager.delete=ROLE_SUPERVISOR,RUN_AS_SERVER
-				sample.contact.ContactManager.getAllByOwner=CONTACT_OWNED_BY_CURRENT_USER,RUN_AS_SERVER
-				sample.contact.ContactManager.save=CONTACT_OWNED_BY_CURRENT_USER,RUN_AS_SERVER
-				sample.contact.ContactManager.getById=ROLE_TELLER,RUN_AS_SERVER
-			</value>
-		</property>
-	</bean>
-
-	<!-- We expect all callers of the backend object to hold the role ROLE_RUN_AS_SERVER -->
-	<bean id="backendContactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
-    	<property name="authenticationManager"><ref local="authenticationManager"/></property>
-    	<property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
-    	<property name="runAsManager"><ref local="runAsManager"/></property>
- 		<property name="objectDefinitionSource">
-			<value>
-				sample.contact.ContactManager.delete=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.getAllByOwner=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.save=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.getById=ROLE_RUN_AS_SERVER
-			</value>
-		</property>
-	</bean>
-
-	<!-- ======================= BUSINESS DEFINITIONS ===================== -->
-
-	<bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
-    	<property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
-	    <property name="interceptorNames">
-      	<list>
-        	<idref local="publicContactManagerSecurity"/>
- 	        <idref local="publicContactManagerTarget"/>
-    	</list>
-	    </property>
-  	</bean>
-
-	<bean id="publicContactManagerTarget" class="sample.contact.ContactManagerFacade">
-    	<property name="backend"><ref local="backendContactManager"/></property>
-	</bean>
-
-	<bean id="backendContactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
-    	<property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
-	    <property name="interceptorNames">
-      	<list>
-        	<idref local="backendContactManagerSecurity"/>
- 	        <idref local="backendContactManagerTarget"/>
-    	</list>
-	    </property>
-  	</bean>
-
-	<bean id="backendContactManagerTarget" class="sample.contact.ContactManagerBackend"/>
-
-</beans>

+ 32 - 45
samples/contacts/src/main/webapp/ca/WEB-INF/web.xml

@@ -3,73 +3,65 @@
 
 <!--
   - Contacts web application
+  -
+  - web.xml for "ca" artifact only.
+  -
   - $Id$
-  - File will be copied into WAR's WEB-INF directory if using container adapter
   -->
 
 <web-app>
 
     <display-name>Contacts Sample Application</display-name>
     
-	<description>
-    	Example of an application secured using Acegi Security System for Spring.
-    </description>
-
 	<!--
 	  - Location of the XML file that defines the root application context
 	  - Applied by ContextLoaderListener.
 	  -->
 	<context-param>
 		<param-name>contextConfigLocation</param-name>
-		<param-value>/WEB-INF/applicationContext.xml</param-value>
+		<param-value>
+			/WEB-INF/applicationContext-acegi-security.xml
+			/WEB-INF/applicationContext-common-business.xml
+			/WEB-INF/applicationContext-common-authorization.xml
+		</param-value>
+	</context-param>
+	
+	<context-param>
+		<param-name>log4jConfigLocation</param-name>
+		<param-value>/WEB-INF/classes/log4j.properties</param-value>
 	</context-param>
 
+	<!-- Obtains Authentication from HttpServletRequest.getPrincipal(),
+		 and puts it into ContextHolder for request duration -->
     <filter>
-        <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
-        <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
-        <init-param>
-            <param-name>targetClass</param-name>
-            <param-value>net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter</param-value>
-        </init-param>
-    </filter>
-
-    <filter>
-        <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
+        <filter-name>Acegi Security System for Spring HttpRequest Integration Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
         <init-param>
             <param-name>targetClass</param-name>
-            <param-value>net.sf.acegisecurity.ui.AutoIntegrationFilter</param-value>
+            <param-value>net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter</param-value>
         </init-param>
     </filter>
 
     <filter-mapping>
-      <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
+      <filter-name>Acegi Security System for Spring HttpRequest Integration Filter</filter-name>
       <url-pattern>/*</url-pattern>
     </filter-mapping>
-
-    <filter-mapping>
-      <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
-      <url-pattern>/*</url-pattern>
-    </filter-mapping>
-
+    
 	<!--
-	  - Loads the root application context of this web app at startup,
-	  - by default from "/WEB-INF/applicationContext.xml".
-	  - Use WebApplicationContextUtils.getWebApplicationContext(servletContext)
-	  - to access it anywhere in the web application, outside of the framework.
+	  - Loads the root application context of this web app at startup.
+	  - The application context is then available via 
+	  - WebApplicationContextUtils.getWebApplicationContext(servletContext).
     -->
 	<listener>
 		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 	</listener>
 
+    <listener>
+		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
+	</listener>
+	
   <!--
-    - Servlet that dispatches request to registered handlers (Controller implementations).
-    - Has its own application context, by default defined in "{servlet-name}-servlet.xml",
-    - i.e. "contacts-servlet.xml".
-    -
-    - A web app can contain any number of such servlets.
-    - Note that this web app does not have a shared root application context,
-    - therefore the DispatcherServlet contexts do not have a common parent.
+	- Provides core MVC application controller. See contacts-servlet.xml.
     -->
 	<servlet>
 		<servlet-name>contacts</servlet-name>
@@ -77,25 +69,20 @@
 		<load-on-startup>1</load-on-startup>
 	</servlet>
 
+  <!--
+    - Provides web services endpoint. See caucho-servlet.xml.
+    -->
 	<servlet>
 		<servlet-name>caucho</servlet-name>
 		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 		<load-on-startup>2</load-on-startup>
 	</servlet>
 
-  <!--
-    - Maps the contacts dispatcher to /*.
-    -
-   -->
 	<servlet-mapping>
     	<servlet-name>contacts</servlet-name>
     	<url-pattern>*.htm</url-pattern>
  	</servlet-mapping>
   
-  <!--
-	- Dispatcher servlet mapping for HTTP remoting via the Caucho protocols,
-	- i.e. Hessian and Burlap (see caucho-servlet.xml for the controllers).
-   -->
 	<servlet-mapping>
 		<servlet-name>caucho</servlet-name>
 		<url-pattern>/caucho/*</url-pattern>
@@ -117,7 +104,7 @@
          <url-pattern>/secure/*</url-pattern>
       </web-resource-collection>
       <auth-constraint>
-         <role-name>ROLE_TELLER</role-name>
+         <role-name>ROLE_USER</role-name>
 	     <role-name>ROLE_SUPERVISOR</role-name>
       </auth-constraint>
     </security-constraint>
@@ -145,7 +132,7 @@
       <role-name>ROLE_SUPERVISOR</role-name>
     </security-role>
     <security-role>
-      <role-name>ROLE_TELLER</role-name>
+      <role-name>ROLE_USER</role-name>
     </security-role>    
 
 </web-app>

+ 5 - 5
samples/contacts/src/main/webapp/ca/login.jsp

@@ -1,5 +1,4 @@
 <%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %>
-<%-- This page will be copied into WAR's root directory if using container adapter --%>
 <html>
   <head>
     <title>Login</title>
@@ -8,11 +7,12 @@
   <body>
     <h1>Login</h1>
 
-	<P>If you've used the standard springsecurity.xml, try these users:
+	<P>Valid users:
 	<P>
-	<P>username <b>marissa</b>, password <b>koala</b> (granted ROLE_SUPERVISOR)
-	<P>username <b>dianne</b>, password <b>emu</b> (not a supervisor)
-	<p>username <b>scott</b>, password <b>wombat</b> (not a supervisor)
+	<P>username <b>marissa</b>, password <b>koala</b>
+	<P>username <b>dianne</b>, password <b>emu</b>
+	<p>username <b>scott</b>, password <b>wombat</b>
+	<p>username <b>peter</b>, password <b>opal</b> (user disabled)
 	<p>
 	
     <%-- this form-login-page form is also used as the 

+ 31 - 129
samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext.xml → samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml

@@ -2,60 +2,40 @@
 <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
 <!--
-  - Application context loaded by ContextLoaderListener if NOT using container adapters
-  - $Id$
-  -
-  -
-  - HAVING PROBLEMS? See the etc/ssl/howto.txt file and read "TROUBLESHOOTING".
+  - Application context containing authentication, channel
+  - security and web URI beans.
   -
+  - Only used by "cas" artifact.
   -
+  - $Id$
   -->
 
 <beans>
 
-	<!-- =================== SECURITY SYSTEM DEFINITIONS ================== -->
-	
-	<!-- RunAsManager -->
-	<bean id="runAsManager" class="net.sf.acegisecurity.runas.RunAsManagerImpl">
-     	<property name="key"><value>my_run_as_password</value></property>
- 	</bean>
-
-	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHENTICATION DEFINITIONS ~~~~~~~~~~~~~~~~~~ -->
-	
-	<bean id="runAsAuthenticationProvider" class="net.sf.acegisecurity.runas.RunAsImplAuthenticationProvider">
-     	<property name="key"><value>my_run_as_password</value></property>
- 	</bean>
+   <!-- ======================== AUTHENTICATION ======================= -->
 
-	<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
-		<property name="providers">
-		  <list>
-		    <ref local="runAsAuthenticationProvider"/>
+   <bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
+      <property name="providers">
+         <list>
 		    <ref local="casAuthenticationProvider"/>
-		  </list>
-		</property>
-	</bean>
+         </list>
+      </property>
+   </bean>
 
-	<bean id="inMemoryDaoImpl" class="net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl">
-  		<property name="userMap">
-			<value>
-				marissa=PASSWORD_NOT_USED,ROLE_TELLER,ROLE_SUPERVISOR
-				dianne=PASSWORD_NOT_USED,ROLE_TELLER
-				scott=PASSWORD_NOT_USED,ROLE_TELLER
-				peter=PASSWORD_NOT_USED_AND_DISABLED_IGNORED,disabled,ROLE_TELLER
-			</value>
-		</property>
-	</bean>
-	
-	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
-		<property name="authenticationManager"><ref local="authenticationManager"/></property>
-		<property name="authenticationEntryPoint"><ref local="basicProcessingFilterEntryPoint"/></property>
-	</bean>
+   <bean id="jdbcDaoImpl" class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
+      <property name="dataSource"><ref bean="dataSource"/></property>
+   </bean>
 
-	<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
-		<property name="realmName"><value>Contacts Realm</value></property>
-	</bean>
+   <bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
+      <property name="authenticationManager"><ref local="authenticationManager"/></property>
+      <property name="authenticationEntryPoint"><ref local="basicProcessingFilterEntryPoint"/></property>
+   </bean>
+
+   <bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
+      <property name="realmName"><value>Contacts Realm</value></property>
+   </bean>
 
-	<bean id="autoIntegrationFilter" class="net.sf.acegisecurity.ui.AutoIntegrationFilter" />
+   <bean id="httpSessionIntegrationFilter" class="net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter"/>
 
 	<bean id="casAuthenticationProvider" class="net.sf.acegisecurity.providers.cas.CasAuthenticationProvider">
 		<property name="casAuthoritiesPopulator"><ref local="casAuthoritiesPopulator"/></property>
@@ -77,7 +57,7 @@
 	</bean>
 
 	<bean id="casAuthoritiesPopulator" class="net.sf.acegisecurity.providers.cas.populator.DaoCasAuthoritiesPopulator">
-		<property name="authenticationDao"><ref local="inMemoryDaoImpl"/></property>
+		<property name="authenticationDao"><ref local="jdbcDaoImpl"/></property>
 	</bean>
 
 	<bean id="casProxyDecider" class="net.sf.acegisecurity.providers.cas.proxy.RejectProxyTickets">
@@ -88,86 +68,9 @@
 		<property name="sendRenew"><value>false</value></property>
 	</bean>
 
-	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
-
-	<!-- An access decision voter that reads ROLE_* configuaration settings -->
-	<bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
-
-	<!-- An access decision voter that reads CONTACT_OWNED_BY_CURRENT_USER configuaration settings -->
-	<bean id="contactSecurityVoter" class="sample.contact.ContactSecurityVoter"/>
-
-	<!-- An access decision manager used by the business objects -->
-	<bean id="businessAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
-   		<property name="allowIfAllAbstainDecisions"><value>false</value></property>
-		<property name="decisionVoters">
-		  <list>
-		    <ref local="roleVoter"/>
-		    <ref local="contactSecurityVoter"/>
-		  </list>
-		</property>
-	</bean>
-
-	<!-- ===================== SECURITY DEFINITIONS ======================= -->
-	
-	<bean id="publicContactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
-    	<property name="authenticationManager"><ref local="authenticationManager"/></property>
-    	<property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
-    	<property name="runAsManager"><ref local="runAsManager"/></property>
- 		<property name="objectDefinitionSource">
-			<value>
-				sample.contact.ContactManager.delete=ROLE_SUPERVISOR,RUN_AS_SERVER
-				sample.contact.ContactManager.getAllByOwner=CONTACT_OWNED_BY_CURRENT_USER,RUN_AS_SERVER
-				sample.contact.ContactManager.save=CONTACT_OWNED_BY_CURRENT_USER,RUN_AS_SERVER
-				sample.contact.ContactManager.getById=ROLE_TELLER,RUN_AS_SERVER
-			</value>
-		</property>
-	</bean>
-
-	<!-- We expect all callers of the backend object to hold the role ROLE_RUN_AS_SERVER -->
-	<bean id="backendContactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
-    	<property name="authenticationManager"><ref local="authenticationManager"/></property>
-    	<property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
-    	<property name="runAsManager"><ref local="runAsManager"/></property>
- 		<property name="objectDefinitionSource">
-			<value>
-				sample.contact.ContactManager.delete=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.getAllByOwner=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.save=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.getById=ROLE_RUN_AS_SERVER
-			</value>
-		</property>
-	</bean>
-
-	<!-- ======================= BUSINESS DEFINITIONS ===================== -->
-
-	<bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
-    	<property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
-	    <property name="interceptorNames">
-      	<list>
-        	<idref local="publicContactManagerSecurity"/>
- 	        <idref local="publicContactManagerTarget"/>
-    	</list>
-	    </property>
-  	</bean>
-
-	<bean id="publicContactManagerTarget" class="sample.contact.ContactManagerFacade">
-    	<property name="backend"><ref local="backendContactManager"/></property>
-	</bean>
-
-	<bean id="backendContactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
-    	<property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
-	    <property name="interceptorNames">
-      	<list>
-        	<idref local="backendContactManagerSecurity"/>
- 	        <idref local="backendContactManagerTarget"/>
-    	</list>
-	    </property>
-  	</bean>
-
-	<bean id="backendContactManagerTarget" class="sample.contact.ContactManagerBackend"/>
-
 	<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
 	
+	<!-- Enabled by default for CAS, as a CAS deployment uses HTTPS -->
 	<bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter">
 		<property name="channelDecisionManager"><ref local="channelDecisionManager"/></property>
  		<property name="filterInvocationDefinitionSource">
@@ -194,6 +97,11 @@
 
 	<!-- ===================== HTTP REQUEST SECURITY ==================== -->
 
+	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
+		<property name="filterSecurityInterceptor"><ref local="filterInvocationInterceptor"/></property>
+		<property name="authenticationEntryPoint"><ref local="casProcessingFilterEntryPoint"/></property>
+	</bean>
+
 	<bean id="casProcessingFilter" class="net.sf.acegisecurity.ui.cas.CasProcessingFilter">
 		<property name="authenticationManager"><ref local="authenticationManager"/></property>
 		<property name="authenticationFailureUrl"><value>/casfailed.jsp</value></property>
@@ -201,11 +109,6 @@
 		<property name="filterProcessesUrl"><value>/j_acegi_cas_security_check</value></property>
 	</bean>
 
-	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
-		<property name="filterSecurityInterceptor"><ref local="filterInvocationInterceptor"/></property>
-		<property name="authenticationEntryPoint"><ref local="casProcessingFilterEntryPoint"/></property>
-	</bean>
-
 	<bean id="casProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
 		<property name="loginUrl"><value>https://localhost:8443/cas/login</value></property>
 		<property name="serviceProperties"><ref local="serviceProperties"/></property>
@@ -215,7 +118,7 @@
    		<property name="allowIfAllAbstainDecisions"><value>false</value></property>
 		<property name="decisionVoters">
 		  <list>
-		    <ref local="roleVoter"/>
+		    <ref bean="roleVoter"/>
 		  </list>
 		</property>
 	</bean>
@@ -226,7 +129,6 @@
 	<bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
     	<property name="authenticationManager"><ref local="authenticationManager"/></property>
     	<property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
-    	<property name="runAsManager"><ref local="runAsManager"/></property>
  		<property name="objectDefinitionSource">
 			<value>
 			    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

+ 45 - 52
samples/contacts/src/main/webapp/cas/WEB-INF/web.xml

@@ -3,17 +3,33 @@
 
 <!--
   - Contacts web application
+  -
+  - web.xml for "cas" artifact only.
+  -
   - $Id$
-  - File will be copied into WAR's WEB-INF directory if NOT using container adapter
   -->
 
 <web-app>
 
     <display-name>Contacts Sample Application</display-name>
     
-	<description>
-    	Example of an application secured using Acegi Security System for Spring.
-    </description>
+	<!--
+	  - Location of the XML file that defines the root application context
+	  - Applied by ContextLoaderListener.
+	  -->
+	<context-param>
+		<param-name>contextConfigLocation</param-name>
+		<param-value>
+			/WEB-INF/applicationContext-acegi-security.xml
+			/WEB-INF/applicationContext-common-business.xml
+			/WEB-INF/applicationContext-common-authorization.xml
+		</param-value>
+	</context-param>
+	
+	<context-param>
+		<param-name>log4jConfigLocation</param-name>
+		<param-value>/WEB-INF/classes/log4j.properties</param-value>
+	</context-param>
 
 	<!-- Required for CAS ProxyTicketReceptor servlet. This is the
 	     URL to CAS' "proxy" actuator, where a PGT and TargetService can
@@ -24,15 +40,6 @@
         <param-value>http://localhost:8433/cas/proxy</param-value>
     </context-param>
 
-	<!--
-	  - Location of the XML file that defines the root application context
-	  - Applied by ContextLoaderListener.
-	  -->
-	<context-param>
-		<param-name>contextConfigLocation</param-name>
-		<param-value>/WEB-INF/applicationContext.xml</param-value>
-	</context-param>
-
    <filter>
         <filter-name>Acegi Channel Processing Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -42,6 +49,7 @@
         </init-param>
     </filter>
 
+	<!-- Responds to HTTP POSTs to j_acegi_cas_security_check URI -->
     <filter>
         <filter-name>Acegi CAS Processing Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -51,6 +59,7 @@
         </init-param>
     </filter>
 
+	<!-- Responds to HTTP requests with a BASIC (RFC 1945) authentication header -->
     <filter>
         <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -60,15 +69,21 @@
         </init-param>
     </filter>
 
+	<!-- Obtains Authentication from HttpSession attribute, puts it into
+		 ContextHolder for request duration, proceeds with request, then
+		 copies Authentication from ContextHolder back into HttpSession -->
     <filter>
-        <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
+        <filter-name>Acegi Security System for Spring HttpSession Integration Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
         <init-param>
             <param-name>targetClass</param-name>
-            <param-value>net.sf.acegisecurity.ui.AutoIntegrationFilter</param-value>
+            <param-value>net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter</param-value>
         </init-param>
     </filter>
 
+	<!-- Provides HTTP request URL security, and also catches
+		 AcegiSecurityExceptions and sends 403 errors (if access denied)
+		 or commences an authentication mechanism as appropriate -->
     <filter>
         <filter-name>Acegi HTTP Request Security Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -77,12 +92,12 @@
             <param-value>net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value>
         </init-param>
     </filter>
-
+	
     <filter-mapping>
       <filter-name>Acegi Channel Processing Filter</filter-name>
       <url-pattern>/*</url-pattern>
     </filter-mapping>
-
+	
     <filter-mapping>
       <filter-name>Acegi CAS Processing Filter</filter-name>
       <url-pattern>/*</url-pattern>
@@ -94,7 +109,7 @@
     </filter-mapping>
 
     <filter-mapping>
-      <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
+      <filter-name>Acegi Security System for Spring HttpSession Integration Filter</filter-name>
       <url-pattern>/*</url-pattern>
     </filter-mapping>
     
@@ -104,23 +119,20 @@
     </filter-mapping>
 
 	<!--
-	  - Loads the root application context of this web app at startup,
-	  - by default from "/WEB-INF/applicationContext.xml".
-	  - Use WebApplicationContextUtils.getWebApplicationContext(servletContext)
-	  - to access it anywhere in the web application, outside of the framework.
+	  - Loads the root application context of this web app at startup.
+	  - The application context is then available via 
+	  - WebApplicationContextUtils.getWebApplicationContext(servletContext).
     -->
 	<listener>
 		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 	</listener>
 
+    <listener>
+		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
+	</listener>
+	
   <!--
-    - Servlet that dispatches request to registered handlers (Controller implementations).
-    - Has its own application context, by default defined in "{servlet-name}-servlet.xml",
-    - i.e. "contacts-servlet.xml".
-    -
-    - A web app can contain any number of such servlets.
-    - Note that this web app does not have a shared root application context,
-    - therefore the DispatcherServlet contexts do not have a common parent.
+	- Provides core MVC application controller. See contacts-servlet.xml.
     -->
 	<servlet>
 		<servlet-name>contacts</servlet-name>
@@ -128,44 +140,25 @@
 		<load-on-startup>1</load-on-startup>
 	</servlet>
 
+  <!--
+    - Provides web services endpoint. See caucho-servlet.xml.
+    -->
 	<servlet>
 		<servlet-name>caucho</servlet-name>
 		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 		<load-on-startup>2</load-on-startup>
 	</servlet>
-	
-	<!-- CAS servlet which receives a proxy-granting ticket from the CAS
-	     server. THIS CAN BE REMOVED IF THE APPLICATION DOESN'T NEED TO 
-	     ACT AS A PROXY -->
-	<servlet>
-		<servlet-name>casproxy</servlet-name>
-		<servlet-class>edu.yale.its.tp.cas.proxy.ProxyTicketReceptor</servlet-class>
-		<load-on-startup>3</load-on-startup>
-	</servlet>
 
-  <!--
-    - Maps the contacts dispatcher to /*.
-    -
-   -->
 	<servlet-mapping>
     	<servlet-name>contacts</servlet-name>
     	<url-pattern>*.htm</url-pattern>
  	</servlet-mapping>
   
-  <!--
-	- Dispatcher servlet mapping for HTTP remoting via the Caucho protocols,
-	- i.e. Hessian and Burlap (see caucho-servlet.xml for the controllers).
-   -->
 	<servlet-mapping>
 		<servlet-name>caucho</servlet-name>
 		<url-pattern>/caucho/*</url-pattern>
 	</servlet-mapping>
 
-	<servlet-mapping>
-		<servlet-name>casproxy</servlet-name>
-		<url-pattern>/casProxy/*</url-pattern>
-	</servlet-mapping>
-
  	<welcome-file-list>
 		<welcome-file>index.jsp</welcome-file>
 	</welcome-file-list>
@@ -174,5 +167,5 @@
       <taglib-uri>/spring</taglib-uri>
       <taglib-location>/WEB-INF/spring.tld</taglib-location>
   	</taglib>
-  
+
 </web-app>

+ 0 - 1
samples/contacts/src/main/webapp/cas/casfailed.jsp

@@ -1,7 +1,6 @@
 <%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %>
 <%@ page import="net.sf.acegisecurity.ui.AbstractProcessingFilter" %>
 <%@ page import="net.sf.acegisecurity.AuthenticationException" %>
-<%-- This page will be copied into WAR's root directory if using CAS --%>
 
 <html>
   <head>

+ 151 - 0
samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-authorization.xml

@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!--
+  - Application context containing authentication beans.
+  -
+  - Used by all artifacts.
+  -
+  - $Id$
+  -->
+
+<beans>
+
+   <!-- ~~~~~~~~~~~~~~~~~~ "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~ -->
+
+   <!-- An access decision voter that reads ROLE_* configuration settings -->
+   <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
+
+   <!-- An access decision voter that reads ACL_CONTACT_READ configuration settings -->
+   <bean id="aclContactReadVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
+      <property name="processConfigAttribute"><value>ACL_CONTACT_READ</value></property>
+      <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
+      <property name="aclManager"><ref local="aclManager"/></property>
+      <property name="requirePermission">
+		<list>
+		    <value>1</value>  <!-- SimpleAclEntry.ADMINISTER -->
+		    <value>2</value>  <!-- SimpleAclEntry.READ -->
+		</list>
+      </property>
+   </bean>
+
+   <!-- An access decision voter that reads ACL_CONTACT_DELETE configuration settings -->
+   <bean id="aclContactDeleteVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
+      <property name="processConfigAttribute"><value>ACL_CONTACT_DELETE</value></property>
+      <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
+      <property name="aclManager"><ref local="aclManager"/></property>
+      <property name="requirePermission">
+		<list>
+		    <value>1</value>  <!-- SimpleAclEntry.ADMINISTER -->
+		    <value>16</value> <!-- SimpleAclEntry.DELETE -->
+		</list>
+      </property>
+   </bean>
+
+   <!-- An access decision voter that reads ACL_CONTACT_ADMIN configuration settings -->
+   <bean id="aclContactAdminVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
+      <property name="processConfigAttribute"><value>ACL_CONTACT_ADMIN</value></property>
+      <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
+      <property name="aclManager"><ref local="aclManager"/></property>
+      <property name="requirePermission">
+		<list>
+		    <value>1</value>  <!-- SimpleAclEntry.ADMINISTER -->
+		</list>
+      </property>
+   </bean>
+
+   <!-- An access decision manager used by the business objects -->
+   <bean id="businessAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
+      <property name="allowIfAllAbstainDecisions"><value>false</value></property>
+      <property name="decisionVoters">
+         <list>
+            <ref local="roleVoter"/>
+            <ref local="aclContactReadVoter"/>
+            <ref local="aclContactDeleteVoter"/>
+            <ref local="aclContactAdminVoter"/>
+         </list>
+      </property>
+   </bean>
+
+   <!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
+
+   <bean id="aclManager" class="net.sf.acegisecurity.acl.AclProviderManager">
+      <property name="providers">
+         <list>
+            <ref local="basicAclProvider"/>
+         </list>
+      </property>
+   </bean>
+
+   <bean id="basicAclProvider" class="net.sf.acegisecurity.acl.basic.BasicAclProvider">
+      <property name="basicAclDao"><ref local="basicAclDao"/></property>
+   </bean>
+
+   <bean id="basicAclDao" class="net.sf.acegisecurity.acl.basic.jdbc.JdbcDaoImpl">
+      <property name="dataSource"><ref bean="dataSource"/></property>
+   </bean>
+
+   <!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
+
+   <bean id="afterInvocationManager" class="net.sf.acegisecurity.afterinvocation.AfterInvocationProviderManager">
+      <property name="providers">
+         <list>
+            <ref local="afterAclRead"/>
+            <ref local="afterAclCollectionRead"/>
+         </list>
+      </property>
+   </bean>
+   
+   <!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
+   <bean id="afterAclCollectionRead" class="net.sf.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">
+      <property name="aclManager"><ref local="aclManager"/></property>
+      <property name="requirePermission">
+		<list>
+		    <value>1</value>  <!-- SimpleAclEntry.ADMINISTER -->
+		    <value>2</value>  <!-- SimpleAclEntry.READ -->
+		</list>
+      </property>
+   </bean>
+   
+   <!-- Processes AFTER_ACL_READ configuration settings -->
+   <bean id="afterAclRead" class="net.sf.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationProvider">
+      <property name="aclManager"><ref local="aclManager"/></property>
+      <property name="requirePermission">
+		<list>
+		    <value>1</value>  <!-- SimpleAclEntry.ADMINISTER -->
+		    <value>2</value>  <!-- SimpleAclEntry.READ -->
+		</list>
+      </property>
+   </bean>
+
+
+   <!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
+
+   <!-- getRandomContact() is public.
+	
+	    The create, getAll, getById etc have ROLE_USER to ensure user is
+		authenticated (all users hold ROLE_USER in this application).
+		
+		The delete and update methods don't need a ROLE_USER as they will
+		ensure the user is authenticated via their ACL_CONTACT_DELETE or
+		ACL_CONTACT_READ attribute, which also ensures the user has permission
+		to the Contact presented as a method argument.
+	-->
+   <bean id="contactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
+      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
+      <property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
+      <property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
+      <property name="objectDefinitionSource">
+         <value>
+				sample.contact.ContactManager.create=ROLE_USER
+				sample.contact.ContactManager.getAllRecipients=ROLE_USER
+				sample.contact.ContactManager.getAll=ROLE_USER,AFTER_ACL_COLLECTION_READ
+				sample.contact.ContactManager.getById=ROLE_USER,AFTER_ACL_READ
+				sample.contact.ContactManager.delete=ACL_CONTACT_DELETE
+				sample.contact.ContactManager.deletePermission=ACL_CONTACT_ADMIN
+				sample.contact.ContactManager.addPermission=ACL_CONTACT_ADMIN
+         </value>
+      </property>
+   </bean>
+
+</beans>

+ 71 - 0
samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-business.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!--
+  - Application context containing business beans.
+  -
+  - Used by all artifacts.
+  -
+  - $Id$
+  -->
+
+<beans>
+
+    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+        <property name="driverClassName">
+            <value>org.hsqldb.jdbcDriver</value>
+        </property>
+        <property name="url">
+            <value>jdbc:hsqldb:mem:contacts</value>
+        </property>
+        <property name="username">
+            <value>sa</value>
+        </property>
+        <property name="password">
+            <value></value>
+        </property>
+    </bean>
+	
+	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+		<property name="dataSource"><ref local="dataSource"/></property>
+	</bean>
+	
+	<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
+    	<property name="transactionManager"><ref bean="transactionManager"/></property>
+		<property name="transactionAttributeSource">
+			<value>
+				sample.contact.ContactManager.create=PROPAGATION_REQUIRED
+				sample.contact.ContactManager.getAllRecipients=PROPAGATION_REQUIRED,readOnly
+				sample.contact.ContactManager.getAll=PROPAGATION_REQUIRED,readOnly
+				sample.contact.ContactManager.getById=PROPAGATION_REQUIRED,readOnly
+				sample.contact.ContactManager.delete=PROPAGATION_REQUIRED
+				sample.contact.ContactManager.deletePermission=PROPAGATION_REQUIRED
+				sample.contact.ContactManager.addPermission=PROPAGATION_REQUIRED
+			</value>
+		</property>
+	</bean>
+
+   <bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
+	   <property name="dataSource"><ref local="dataSource"/></property>
+   </bean>
+   
+   <bean id="contactDao" class="sample.contact.ContactDaoSpring">
+	   <property name="dataSource"><ref local="dataSource"/></property>
+   </bean>
+
+   <bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
+      <property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
+      <property name="interceptorNames">
+         <list>
+            <idref local="transactionInterceptor"/>
+            <idref bean="contactManagerSecurity"/>
+            <idref local="contactManagerTarget"/>
+         </list>
+      </property>
+   </bean>
+
+   <bean id="contactManagerTarget" class="sample.contact.ContactManagerBackend">
+	   <property name="contactDao"><ref local="contactDao"/></property>
+   </bean>
+
+</beans>

+ 29 - 0
samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml

@@ -3,6 +3,7 @@
 
 <!--
   - Application context definition for "contacts" DispatcherServlet.
+  -
   - $Id$
   -->
 
@@ -22,6 +23,16 @@
     	<property name="contactManager"><ref bean="contactManager"/></property>
  	</bean>
 
+    <bean id="adminPermissionController" class="sample.contact.AdminPermissionController">
+    	<property name="contactManager"><ref bean="contactManager"/></property>
+    	<property name="aclManager"><ref bean="aclManager"/></property>
+ 	</bean>
+
+    <bean id="deletePermissionController" class="sample.contact.DeletePermissionController">
+    	<property name="contactManager"><ref bean="contactManager"/></property>
+    	<property name="aclManager"><ref bean="aclManager"/></property>
+ 	</bean>
+
     <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
         <property name="mappings">
             <props>
@@ -29,11 +40,17 @@
                 <prop key="/secure/add.htm">secureAddForm</prop>
                 <prop key="/secure/index.htm">secureIndexController</prop>
                 <prop key="/secure/del.htm">secureDeleteController</prop>
+                <prop key="/secure/adminPermission.htm">adminPermissionController</prop>
+                <prop key="/secure/deletePermission.htm">deletePermissionController</prop>
+                <prop key="/secure/addPermission.htm">addPermissionForm</prop>
 			</props>
         </property>
     </bean>
 
     <bean id="addValidator" class="sample.contact.WebContactValidator"/>
+    
+	<bean id="addPermissionValidator" class="sample.contact.AddPermissionValidator"/>
+
     <bean id="secureAddForm" class="sample.contact.WebContactAddController">
         <property name="sessionForm"><value>true</value></property>
         <property name="commandName"><value>webContact</value></property>
@@ -46,6 +63,18 @@
         </property>
     </bean>
 
+    <bean id="addPermissionForm" class="sample.contact.AddPermissionController">
+        <property name="sessionForm"><value>true</value></property>
+        <property name="commandName"><value>addPermission</value></property>
+        <property name="commandClass"><value>sample.contact.AddPermission</value></property>
+        <property name="validator"><ref bean="addPermissionValidator"/></property>
+        <property name="formView"><value>addPermission</value></property>
+        <property name="successView"><value>index.htm</value></property>
+        <property name="contactManager">
+            <ref bean="contactManager"/>
+        </property>
+    </bean>
+
 	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 		<property name="prefix"><value>/WEB-INF/jsp/</value></property>
 		<property name="suffix"><value>.jsp</value></property>

+ 55 - 0
samples/contacts/src/main/webapp/common/WEB-INF/jsp/addPermission.jsp

@@ -0,0 +1,55 @@
+<%@ include file="/WEB-INF/jsp/include.jsp" %>
+<html>
+<head><title>Add Permission</title></head>
+<body>
+<h1>Add Permission</h1>
+<form method="post">
+  <table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
+    <tr>
+      <td alignment="right" width="20%">Contact:</td>
+      <td width="60%"><c:out value="${addPermission.contact}"/></td>
+    </tr>
+    <tr>
+      <td alignment="right" width="20%">Recipient:</td>
+      <spring:bind path="addPermission.recipient">
+        <td width="20%">
+		    <select name="<c:out value="${status.expression}"/>">
+		      <c:forEach var="thisRecipient" items="${recipients}">
+		        <option <c:if test="${thisRecipient.key == status.value}">selected</c:if> value="<c:out value="${thisRecipient.key}"/>">
+		        <c:out value="${thisRecipient.value}"/></option>
+			    </c:forEach>
+		    </select>
+        </td>
+        <td width="60%">
+          <font color="red"><c:out value="${status.errorMessage}"/></font>
+        </td>
+      </spring:bind>
+    </tr>
+    <tr>
+      <td alignment="right" width="20%">Permission:</td>
+      <spring:bind path="addPermission.permission">
+        <td width="20%">
+		    <select name="<c:out value="${status.expression}"/>">
+		      <c:forEach var="thisPermission" items="${permissions}">
+		        <option <c:if test="${thisPermission.key == status.value}">selected</c:if> value="<c:out value="${thisPermission.key}"/>">
+		        <c:out value="${thisPermission.value}"/></option>
+			    </c:forEach>
+		    </select>
+        </td>
+        <td width="60%">
+          <font color="red"><c:out value="${status.errorMessage}"/></font>
+        </td>
+      </spring:bind>
+    </tr>
+  </table>
+  <br>
+  <spring:hasBindErrors name="webContact">
+    <b>Please fix all errors!</b>
+  </spring:hasBindErrors>
+  <br><br>
+  <input name="execute" type="submit" alignment="center" value="Execute">
+</form>
+<p>
+<A HREF="<c:url value="adminPermission.htm"><c:param name="contactId" value="${addPermission.contact.id}"/></c:url>">Admin Permission</A> <a href="<c:url value="index.htm"/>">Manage</a>
+</body>
+</html>

+ 39 - 0
samples/contacts/src/main/webapp/common/WEB-INF/jsp/adminPermission.jsp

@@ -0,0 +1,39 @@
+<%@ page import="net.sf.acegisecurity.acl.basic.SimpleAclEntry" %>
+<%@ include file="/WEB-INF/jsp/include.jsp" %>
+
+<html>
+<head><title>Administer Permissions</title></head>
+<body>
+<h1>Administer Permissions</h1>
+<P>
+<code>
+<c:out value="${model.contact}"/>
+</code>
+<P>
+<table cellpadding=3 border=0>
+<c:forEach var="acl" items="${model.acls}">
+  <c:if test="${acl.class.name eq 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'}">
+    <tr>
+      <td>
+        <code>
+          <%
+            SimpleAclEntry simpleAcl = ((SimpleAclEntry) pageContext.getAttribute("acl"));
+            String permissionBlock = simpleAcl.printPermissionsBlock(); 
+          %>
+          <%= permissionBlock %>
+          [<c:out value="${acl.mask}"/>]
+          <c:out value="${acl.recipient}"/>
+        </code>
+      </td>
+      <td>
+      <!-- This application doesn't use ACL inheritance, so we can safely use
+           the model's contact and know it was directly assigned the ACL -->
+        <A HREF="<c:url value="deletePermission.htm"><c:param name="contactId" value="${model.contact.id}"/><c:param name="recipient" value="${acl.recipient}"/></c:url>">Del</A>
+      </td>
+    </tr>
+  </c:if>
+</c:forEach>
+</table>
+<p><a href="<c:url value="addPermission.htm"><c:param name="contactId" value="${model.contact.id}"/></c:url>">Add Permission</a>   <a href="<c:url value="index.htm"/>">Manage</a>
+</body>
+</html>

+ 18 - 0
samples/contacts/src/main/webapp/common/WEB-INF/jsp/deletePermission.jsp

@@ -0,0 +1,18 @@
+<%@ page import="net.sf.acegisecurity.acl.basic.SimpleAclEntry" %>
+<%@ include file="/WEB-INF/jsp/include.jsp" %>
+
+<html>
+<head><title>Permission Deleted</title></head>
+<body>
+<h1>Permission Deleted</h1>
+<P>
+<code>
+<c:out value="${model.contact}"/>
+</code>
+<P>
+<code>
+<c:out value="${model.recipient}"/>
+</code>
+<p><a href="<c:url value="index.htm"/>">Manage</a>
+</body>
+</html>

+ 31 - 17
samples/contacts/src/main/webapp/common/WEB-INF/jsp/hello.jsp

@@ -4,28 +4,42 @@
 <head><title>Contacts Security Demo</title></head>
 <body>
 <h1>Contacts Security Demo</h1>
-<p>This is a very simple application to demonstrate the Acegi Security System for Spring.
-The application manages contacts, partitioned based on the user that owns them.
-Users may only manage their own contacts, and only users with ROLE_SUPERVISOR
-are allowed to delete their contacts. It also demonstrates how to configure
-server-side secure objects so they can only be accessed via a public facade.
+<P>Contacts demonstrates the following central Acegi Security capabilities:
+<ul>
+<li><b>Role-based security</b>. Each principal is a member of certain roles,
+    which are used to restrict access to certain secure objects.</li>
+<li><b>Domain object instance security</b>. The <code>Contact</code>, the
+    main domain object in the application, has an access control list (ACL)
+    that indicates who is allowed read, administer and delete the object.</li>
+<li><b>Method invocation security</b>. The <code>ContactManager</code> service
+   layer bean has a number of secured (protected) and public (unprotected)
+   methods.</li>
+<li><b>Web request security</b>. The <code>/secure</code> URI path is protected
+   by Acegi Security from principals not holding the 
+   <code>ROLE_USER</code> granted authority.</li>
+<li><b>Security unaware application objects</b>. None of the objects
+   are aware of the security being implemented by Acegi Security. *</li>
+<li><b>Security taglib usage</b>. All of the JSPs use Acegi Security's
+   taglib to evaluate security information. *</li>
+<li><b>Fully declarative security</b>. Every capability is configured in 
+   the application context using standard Acegi Security classes. *</li>
+<li><b>Database-sourced security data</b>. All of the user, role and ACL
+   information is obtained from an in-memory JDBC-compliant database.</li>
+</ul>
 
-<P>If you deployed the contacts-container-adapter.war file, the application
-automatically extracts the principal from the web container (which should be 
-configured with a suitable Acegi Security System for Spring adapter). If
-you're using the standard contacts.war file, the application is entirely
-self-contained and you don't need to do anything special with your web
-container. If you're using the contacts-cas.war file, please review the
-setup in samples/contacts/etc/cas/applicationContext.xml for your CAS server
-and if necessary rebuild using the Contacts application's build.xml.
+* As the application provides an "ACL Administration" use case, those
+classes are necessarily aware of security. But no business use cases are.
 
-<P>This application also demonstrates a public method, which is used to select
-the random contact that is shown below:
-<P>
+<p>Please excuse the lack of look 'n' feel polish in this application.
+It is about security, after all! :-)
+
+<p>To demonstrate a public method on <code>ContactManager</code>,
+here's a random <code>Contact</code>:
+<p>
 <code>
 <c:out value="${contact}"/>
 </code>
-<p>
+<p>Get started by clicking "Manage"...
 <p><A HREF="<c:url value="secure/index.htm"/>">Manage</a>
 <A HREF="<c:url value="secure/debug.jsp"/>">Debug</a>
 </body>

+ 7 - 4
samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp

@@ -3,7 +3,7 @@
 <html>
 <head><title>Your Contacts</title></head>
 <body>
-<h1><c:out value="${model.user}"/>'s Contacts</h1>
+<h1><authz:authentication operation="principal"/>'s Contacts</h1>
 <P>
 <table cellpadding=3 border=0>
 <tr><td><b>id</b></td><td><b>Name</b></td><td><b>Email</b></td></tr>
@@ -18,9 +18,12 @@
   <td>
       <c:out value="${contact.email}"/>
   </td>
-  <authz:authorize ifAllGranted="ROLE_SUPERVISOR">
-    <td><A HREF="<c:url value="del.htm"><c:param name="id" value="${contact.id}"/></c:url>">Del</A></td>
-  </authz:authorize>
+  <authz:acl domainObject="${contact}" hasPermission="16,1">
+    <td><A HREF="<c:url value="del.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Del</A></td>
+  </authz:acl>
+  <authz:acl domainObject="${contact}" hasPermission="1">
+    <td><A HREF="<c:url value="adminPermission.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Admin Permission</A></td>
+  </authz:acl>
   </tr>
 </c:forEach>
 </table>

+ 4 - 5
samples/contacts/src/main/webapp/common/secure/debug.jsp

@@ -27,21 +27,20 @@ if (context != null) { %>
 			if (auth instanceof AuthByAdapter) { %>
 				<BR><B>SUCCESS! Your container adapter appears to be properly configured!</B><BR><BR>
 <%			} else { %>
-				<BR><B>SUCCESS! Your web filter appears to be properly configured!</B><BR>
+				<BR><B>SUCCESS! Your web filters appear to be properly configured!</B><BR>
 <%			}
 			
 		} else { %>
 			Authentication object is null.<BR>
-			This is an error and your container adapter will not operate properly until corrected.<BR><BR>
+			This is an error and your Acegi Security application will not operate properly until corrected.<BR><BR>
 <%		}
 	} else { %>
 		<B>ContextHolder does not contain a SecureContext.</B><BR>
-		This is an error and your container adapter will not operate properly until corrected.<BR><BR>
+		This is an error and your Acegi Security application will not operate properly until corrected.<BR><BR>
 <%	}
 } else { %>
 	<B>ContextHolder on ContextHolder is null.</B><BR>
-	This indicates improper setup of the container adapter. Refer to the reference documentation.<BR>
-	Also ensure the correct subclass of AbstractMvcIntegrationInterceptor is being used for your container.<BR>
+	This indicates improper setup of the Acegi Security application. Refer to the reference documentation.<BR>
 <%}
 %>
 

+ 19 - 118
samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext.xml → samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml

@@ -2,54 +2,34 @@
 <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
 <!--
-  - Application context loaded by ContextLoaderListener if NOT using container adapters
+  - Application context containing authentication, channel
+  - security and web URI beans.
+  -
+  - Only used by "filter" artifact.
+  -
   - $Id$
   -->
 
 <beans>
 
-   <!-- =================== SECURITY SYSTEM DEFINITIONS ================== -->
-
-   <!-- RunAsManager -->
-   <bean id="runAsManager" class="net.sf.acegisecurity.runas.RunAsManagerImpl">
-      <property name="key"><value>my_run_as_password</value></property>
-   </bean>
-
-   <!-- ~~~~~~~~~~~~~~~~~~~~ AUTHENTICATION DEFINITIONS ~~~~~~~~~~~~~~~~~~ -->
-
-   <bean id="runAsAuthenticationProvider" class="net.sf.acegisecurity.runas.RunAsImplAuthenticationProvider">
-      <property name="key"><value>my_run_as_password</value></property>
-   </bean>
+   <!-- ======================== AUTHENTICATION ======================= -->
 
    <bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
       <property name="providers">
          <list>
-            <ref local="runAsAuthenticationProvider"/>
             <ref local="daoAuthenticationProvider"/>
          </list>
       </property>
    </bean>
 
-   <!-- Passwords encoded using MD5, NOT in Base64 format, with null as salt
-       Encoded password for marissa is "koala"
-       Encoded password for dianne is "emu"
-       Encoded password for scott is "wombat"
-       Encoded password for peter is "opal" -->
-   <bean id="inMemoryDaoImpl" class="net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl">
-      <property name="userMap">
-         <value>
-				marissa=a564de63c2d0da68cf47586ee05984d7,ROLE_TELLER,ROLE_SUPERVISOR
-				dianne=65d15fe9156f9c4bbffd98085992a44e,ROLE_TELLER
-				scott=2b58af6dddbd072ed27ffc86725d7d3a,ROLE_TELLER
-				peter=22b5c9accc6e1ba628cedc63a72d57f8,disabled,ROLE_TELLER
-         </value>
-      </property>
+   <bean id="jdbcDaoImpl" class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
+      <property name="dataSource"><ref bean="dataSource"/></property>
    </bean>
 
    <bean id="passwordEncoder" class="net.sf.acegisecurity.providers.encoding.Md5PasswordEncoder"/>
 
    <bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
-      <property name="authenticationDao"><ref local="inMemoryDaoImpl"/></property>
+      <property name="authenticationDao"><ref local="jdbcDaoImpl"/></property>
       <property name="userCache"><ref local="userCache"/></property>
       <property name="passwordEncoder"><ref local="passwordEncoder"/></property>
    </bean>
@@ -70,85 +50,7 @@
       <property name="realmName"><value>Contacts Realm</value></property>
    </bean>
 
-   <bean id="autoIntegrationFilter" class="net.sf.acegisecurity.ui.AutoIntegrationFilter"/>
-
-   <!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
-
-   <!-- An access decision voter that reads ROLE_* configuration settings -->
-   <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
-
-   <!-- An access decision voter that reads CONTACT_OWNED_BY_CURRENT_USER configuration settings -->
-   <bean id="contactSecurityVoter" class="sample.contact.ContactSecurityVoter"/>
-
-   <!-- An access decision manager used by the business objects -->
-   <bean id="businessAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
-      <property name="allowIfAllAbstainDecisions"><value>false</value></property>
-      <property name="decisionVoters">
-         <list>
-            <ref local="roleVoter"/>
-            <ref local="contactSecurityVoter"/>
-         </list>
-      </property>
-   </bean>
-
-   <!-- ===================== SECURITY DEFINITIONS ======================= -->
-
-   <bean id="publicContactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
-      <property name="authenticationManager"><ref local="authenticationManager"/></property>
-      <property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
-      <property name="runAsManager"><ref local="runAsManager"/></property>
-      <property name="objectDefinitionSource">
-         <value>
-				sample.contact.ContactManager.delete=ROLE_SUPERVISOR,RUN_AS_SERVER
-				sample.contact.ContactManager.getAllByOwner=CONTACT_OWNED_BY_CURRENT_USER,RUN_AS_SERVER
-				sample.contact.ContactManager.save=CONTACT_OWNED_BY_CURRENT_USER,RUN_AS_SERVER
-				sample.contact.ContactManager.getById=ROLE_TELLER,RUN_AS_SERVER
-         </value>
-      </property>
-   </bean>
-
-   <!-- We expect all callers of the backend object to hold the role ROLE_RUN_AS_SERVER -->
-   <bean id="backendContactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
-      <property name="authenticationManager"><ref local="authenticationManager"/></property>
-      <property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
-      <property name="runAsManager"><ref local="runAsManager"/></property>
-      <property name="objectDefinitionSource">
-         <value>
-				sample.contact.ContactManager.delete=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.getAllByOwner=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.save=ROLE_RUN_AS_SERVER
-				sample.contact.ContactManager.getById=ROLE_RUN_AS_SERVER
-         </value>
-      </property>
-   </bean>
-
-   <!-- ======================= BUSINESS DEFINITIONS ===================== -->
-
-   <bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
-      <property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
-      <property name="interceptorNames">
-         <list>
-            <idref local="publicContactManagerSecurity"/>
-            <idref local="publicContactManagerTarget"/>
-         </list>
-      </property>
-   </bean>
-
-   <bean id="publicContactManagerTarget" class="sample.contact.ContactManagerFacade">
-      <property name="backend"><ref local="backendContactManager"/></property>
-   </bean>
-
-   <bean id="backendContactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
-      <property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
-      <property name="interceptorNames">
-         <list>
-            <idref local="backendContactManagerSecurity"/>
-            <idref local="backendContactManagerTarget"/>
-         </list>
-      </property>
-   </bean>
-
-   <bean id="backendContactManagerTarget" class="sample.contact.ContactManagerBackend"/>
+   <bean id="httpSessionIntegrationFilter" class="net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter"/>
 
    <!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
 
@@ -182,18 +84,18 @@
 
    <!-- ===================== HTTP REQUEST SECURITY ==================== -->
 
+   <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
+      <property name="filterSecurityInterceptor"><ref local="filterInvocationInterceptor"/></property>
+      <property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
+   </bean>
+
    <bean id="authenticationProcessingFilter" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
-      <property name="authenticationManager"><ref local="authenticationManager"/></property>
+      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
       <property name="authenticationFailureUrl"><value>/acegilogin.jsp?login_error=1</value></property>
       <property name="defaultTargetUrl"><value>/</value></property>
       <property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
    </bean>
 
-   <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
-      <property name="filterSecurityInterceptor"><ref local="filterInvocationInterceptor"/></property>
-      <property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
-   </bean>
-
    <bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
       <property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
       <property name="forceHttps"><value>false</value></property>
@@ -203,7 +105,7 @@
       <property name="allowIfAllAbstainDecisions"><value>false</value></property>
       <property name="decisionVoters">
          <list>
-            <ref local="roleVoter"/>
+            <ref bean="roleVoter"/>
          </list>
       </property>
    </bean>
@@ -212,14 +114,13 @@
         The FilterSecurityInterceptor will work from the top of the list down to the FIRST pattern that matches the request URL.
         Accordingly, you should place MOST SPECIFIC (ie a/b/c/d.*) expressions first, with LEAST SPECIFIC (ie a/.*) expressions last -->
    <bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
-      <property name="authenticationManager"><ref local="authenticationManager"/></property>
+      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
       <property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
-      <property name="runAsManager"><ref local="runAsManager"/></property>
       <property name="objectDefinitionSource">
          <value>
 			    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 				\A/secure/super.*\Z=ROLE_WE_DONT_HAVE
-				\A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_TELLER
+				\A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_USER
          </value>
       </property>
    </bean>

+ 37 - 29
samples/contacts/src/main/webapp/filter/WEB-INF/web.xml

@@ -3,27 +3,35 @@
 
 <!--
   - Contacts web application
+  -
+  - web.xml for "filter" artifact only.
+  -
   - $Id$
-  - File will be copied into WAR's WEB-INF directory if NOT using container adapter
   -->
 
 <web-app>
 
     <display-name>Contacts Sample Application</display-name>
     
-	<description>
-    	Example of an application secured using Acegi Security System for Spring.
-    </description>
-
 	<!--
 	  - Location of the XML file that defines the root application context
 	  - Applied by ContextLoaderListener.
 	  -->
 	<context-param>
 		<param-name>contextConfigLocation</param-name>
-		<param-value>/WEB-INF/applicationContext.xml</param-value>
+		<param-value>
+			/WEB-INF/applicationContext-acegi-security.xml
+			/WEB-INF/applicationContext-common-business.xml
+			/WEB-INF/applicationContext-common-authorization.xml
+		</param-value>
 	</context-param>
 	
+	<context-param>
+		<param-name>log4jConfigLocation</param-name>
+		<param-value>/WEB-INF/classes/log4j.properties</param-value>
+	</context-param>
+
+   <!-- The <filter-mapping> to this filter is disabled by default -->
    <filter>
         <filter-name>Acegi Channel Processing Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -33,6 +41,7 @@
         </init-param>
     </filter>
 
+	<!-- Responds to HTTP POSTs to j_acegi_security_check URI -->
     <filter>
         <filter-name>Acegi Authentication Processing Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -42,6 +51,7 @@
         </init-param>
     </filter>
 
+	<!-- Responds to HTTP requests with a BASIC (RFC 1945) authentication header -->
     <filter>
         <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -51,15 +61,21 @@
         </init-param>
     </filter>
 
+	<!-- Obtains Authentication from HttpSession attribute, puts it into
+		 ContextHolder for request duration, proceeds with request, then
+		 copies Authentication from ContextHolder back into HttpSession -->
     <filter>
-        <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
+        <filter-name>Acegi Security System for Spring HttpSession Integration Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
         <init-param>
             <param-name>targetClass</param-name>
-            <param-value>net.sf.acegisecurity.ui.AutoIntegrationFilter</param-value>
+            <param-value>net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter</param-value>
         </init-param>
     </filter>
 
+	<!-- Provides HTTP request URL security, and also catches
+		 AcegiSecurityExceptions and sends 403 errors (if access denied)
+		 or commences an authentication mechanism as appropriate -->
     <filter>
         <filter-name>Acegi HTTP Request Security Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -89,7 +105,7 @@
     </filter-mapping>
 
     <filter-mapping>
-      <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
+      <filter-name>Acegi Security System for Spring HttpSession Integration Filter</filter-name>
       <url-pattern>/*</url-pattern>
     </filter-mapping>
     
@@ -99,23 +115,20 @@
     </filter-mapping>
 
 	<!--
-	  - Loads the root application context of this web app at startup,
-	  - by default from "/WEB-INF/applicationContext.xml".
-	  - Use WebApplicationContextUtils.getWebApplicationContext(servletContext)
-	  - to access it anywhere in the web application, outside of the framework.
+	  - Loads the root application context of this web app at startup.
+	  - The application context is then available via 
+	  - WebApplicationContextUtils.getWebApplicationContext(servletContext).
     -->
 	<listener>
 		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 	</listener>
+
+    <listener>
+		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
+	</listener>
 	
   <!--
-    - Servlet that dispatches request to registered handlers (Controller implementations).
-    - Has its own application context, by default defined in "{servlet-name}-servlet.xml",
-    - i.e. "contacts-servlet.xml".
-    -
-    - A web app can contain any number of such servlets.
-    - Note that this web app does not have a shared root application context,
-    - therefore the DispatcherServlet contexts do not have a common parent.
+	- Provides core MVC application controller. See contacts-servlet.xml.
     -->
 	<servlet>
 		<servlet-name>contacts</servlet-name>
@@ -123,25 +136,20 @@
 		<load-on-startup>1</load-on-startup>
 	</servlet>
 
+  <!--
+    - Provides web services endpoint. See caucho-servlet.xml.
+    -->
 	<servlet>
 		<servlet-name>caucho</servlet-name>
 		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 		<load-on-startup>2</load-on-startup>
 	</servlet>
 
-  <!--
-    - Maps the contacts dispatcher to /*.
-    -
-   -->
 	<servlet-mapping>
     	<servlet-name>contacts</servlet-name>
     	<url-pattern>*.htm</url-pattern>
  	</servlet-mapping>
   
-  <!--
-	- Dispatcher servlet mapping for HTTP remoting via the Caucho protocols,
-	- i.e. Hessian and Burlap (see caucho-servlet.xml for the controllers).
-   -->
 	<servlet-mapping>
 		<servlet-name>caucho</servlet-name>
 		<url-pattern>/caucho/*</url-pattern>
@@ -155,5 +163,5 @@
       <taglib-uri>/spring</taglib-uri>
       <taglib-location>/WEB-INF/spring.tld</taglib-location>
   	</taglib>
-  
+
 </web-app>

+ 5 - 5
samples/contacts/src/main/webapp/filter/acegilogin.jsp

@@ -1,7 +1,6 @@
 <%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %>
 <%@ page import="net.sf.acegisecurity.ui.AbstractProcessingFilter" %>
 <%@ page import="net.sf.acegisecurity.AuthenticationException" %>
-<%-- This page will be copied into WAR's root directory if NOT using container adapter --%>
 
 <html>
   <head>
@@ -11,11 +10,12 @@
   <body>
     <h1>Login</h1>
 
-	<P>If you've used the standard springsecurity.xml, try these users:
+	<P>Valid users:
 	<P>
-	<P>username <b>marissa</b>, password <b>koala</b> (granted ROLE_SUPERVISOR)
-	<P>username <b>dianne</b>, password <b>emu</b> (not a supervisor)
-	<p>username <b>scott</b>, password <b>wombat</b> (not a supervisor)
+	<P>username <b>marissa</b>, password <b>koala</b>
+	<P>username <b>dianne</b>, password <b>emu</b>
+	<p>username <b>scott</b>, password <b>wombat</b>
+	<p>username <b>peter</b>, password <b>opal</b> (user disabled)
 	<p>
 	
     <%-- this form-login-page form is also used as the