diff --git a/changelog.txt b/changelog.txt index ea620f6055..e0c9434c67 100644 --- a/changelog.txt +++ b/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 diff --git a/samples/contacts/src/main/java/sample/contact/AddPermission.java b/samples/contacts/src/main/java/sample/contact/AddPermission.java new file mode 100644 index 0000000000..01dcf46d7a --- /dev/null +++ b/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; + } +} diff --git a/samples/contacts/src/main/java/sample/contact/AddPermissionController.java b/samples/contacts/src/main/java/sample/contact/AddPermissionController.java new file mode 100644 index 0000000000..ddc5ca8c10 --- /dev/null +++ b/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; + } +} diff --git a/samples/contacts/src/main/java/sample/contact/AddPermissionValidator.java b/samples/contacts/src/main/java/sample/contact/AddPermissionValidator.java new file mode 100644 index 0000000000..69218867ec --- /dev/null +++ b/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)."); + } + } + } +} diff --git a/samples/contacts/src/main/java/sample/contact/AdminPermissionController.java b/samples/contacts/src/main/java/sample/contact/AdminPermissionController.java new file mode 100644 index 0000000000..7bcd3ee23e --- /dev/null +++ b/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); + } +} diff --git a/samples/contacts/src/main/java/sample/contact/ClientApplication.java b/samples/contacts/src/main/java/sample/contact/ClientApplication.java index 319b003640..c9bf2090b3 100644 --- a/samples/contacts/src/main/java/sample/contact/ClientApplication.java +++ b/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. - * + * *

* Based on Spring's JPetStore sample, written by Juergen Hoeller. *

@@ -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) { diff --git a/samples/contacts/src/main/java/sample/contact/Contact.java b/samples/contacts/src/main/java/sample/contact/Contact.java index 5b92b732ba..343149e430 100644 --- a/samples/contacts/src/main/java/sample/contact/Contact.java +++ b/samples/contacts/src/main/java/sample/contact/Contact.java @@ -17,10 +17,6 @@ package sample.contact; /** * Represents a contact. - * - *

- * id and owner are immutable. - *

* * @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(); } diff --git a/samples/contacts/src/main/java/sample/contact/ContactDao.java b/samples/contacts/src/main/java/sample/contact/ContactDao.java new file mode 100644 index 0000000000..fff885cfbd --- /dev/null +++ b/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 null if not + * found + */ + public Integer lookupAclObjectIdentity(Contact contact); + + public void update(Contact contact); +} diff --git a/samples/contacts/src/main/java/sample/contact/ContactDaoSpring.java b/samples/contacts/src/main/java/sample/contact/ContactDaoSpring.java new file mode 100644 index 0000000000..51d5c59276 --- /dev/null +++ b/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"); + } + } +} diff --git a/samples/contacts/src/main/java/sample/contact/ContactManager.java b/samples/contacts/src/main/java/sample/contact/ContactManager.java index 8615d41a38..b5cfff6c4e 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactManager.java +++ b/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 List getAllRecipients(); public Contact getById(Integer id); - public Integer getNextId(); - 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); } diff --git a/samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java b/samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java index 3999f30544..db8811e62a 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java +++ b/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. - * - *

- * As a backend, it never faces the public callers. It is always accessed via - * the {@link ContactManagerFacade}. - *

- * - *

- * This facade approach is not really necessary in this application, and is - * done simply to demonstrate granting additional authorities via the - * RunAsManager. - *

+ * 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); - - if (contact.getOwner().equals(owner)) { - list.add(contact); - } - } - - if (list.size() == 0) { - return null; - } else { - return (Contact[]) list.toArray(new Contact[list.size()]); - } + public List getAll() { + return contactDao.findAll(); + } + + public List getAllRecipients() { + List list = contactDao.findAllPrincipals(); + list.addAll(contactDao.findAllRoles()); + + 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 void setContactDao(ContactDao contactDao) { + this.contactDao = contactDao; + } + + public ContactDao getContactDao() { + return contactDao; } /** - * 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(); - } - } - - return new Integer(max + 1); - } - - /** - * 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; } - /** - * Security system expects ROLE_RUN_AS_SERVER - * - * @param contact DOCUMENT ME! - */ + 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); + } + 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(); } } diff --git a/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java b/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java deleted file mode 100644 index 794e8a8756..0000000000 --- a/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java +++ /dev/null @@ -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. - * - *

- * 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 RunAsManager assigning an additional - * GrantedAuthority that is required to call its methods. - *

- * - * @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. - * - *

- * 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. - *

- * - * @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); - } -} diff --git a/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java b/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java deleted file mode 100644 index 8189a6a8c9..0000000000 --- a/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java +++ /dev/null @@ -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. - * - *

- * If the {@link ConfigAttribute#getAttribute()} has a value of - * CONTACT_OWNED_BY_CURRENT_USER, 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. - *

- * - *

- * All comparisons are case sensitive. - *

- * - * @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; - } -} diff --git a/samples/contacts/src/main/java/sample/contact/DataSourcePopulator.java b/samples/contacts/src/main/java/sample/contact/DataSourcePopulator.java new file mode 100644 index 0000000000..2f5d0fb025 --- /dev/null +++ b/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');"); + } +} diff --git a/samples/contacts/src/main/java/sample/contact/DeleteController.java b/samples/contacts/src/main/java/sample/contact/DeleteController.java index d1bcdecbfd..d6fbbc4673 100644 --- a/samples/contacts/src/main/java/sample/contact/DeleteController.java +++ b/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); diff --git a/samples/contacts/src/main/java/sample/contact/DeletePermissionController.java b/samples/contacts/src/main/java/sample/contact/DeletePermissionController.java new file mode 100644 index 0000000000..78b73c8e14 --- /dev/null +++ b/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); + } +} diff --git a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java b/samples/contacts/src/main/java/sample/contact/SecureIndexController.java index 6b25ede3ba..51b5629b50 100644 --- a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java +++ b/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()); + List myContactsList = contactManager.getAll(); + Contact[] myContacts; - if (null == secureContext) { - throw new AuthenticationCredentialsNotFoundException( - "Authentication credentials were not found in the " - + "SecureContext"); + if (myContactsList.size() == 0) { + myContacts = null; + } else { + myContacts = (Contact[]) myContactsList.toArray(new Contact[] {}); } - // 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(); - - if (auth.getPrincipal() instanceof UserDetails) { - username = ((UserDetails) auth.getPrincipal()).getUsername(); - } - - 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); } diff --git a/samples/contacts/src/main/java/sample/contact/WebContactAddController.java b/samples/contacts/src/main/java/sample/contact/WebContactAddController.java index f26613611c..1403c5ff07 100644 --- a/samples/contacts/src/main/java/sample/contact/WebContactAddController.java +++ b/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())); } diff --git a/samples/contacts/src/main/java/sample/contact/WebContactValidator.java b/samples/contacts/src/main/java/sample/contact/WebContactValidator.java index 29b7745871..b30069849f 100644 --- a/samples/contacts/src/main/java/sample/contact/WebContactValidator.java +++ b/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."); } } } diff --git a/samples/contacts/src/main/resources/log4j.properties b/samples/contacts/src/main/resources/log4j.properties new file mode 100644 index 0000000000..7b144a76aa --- /dev/null +++ b/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 diff --git a/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml b/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml new file mode 100644 index 0000000000..c826b46975 --- /dev/null +++ b/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + my_password + + + + + + + + + + + + + diff --git a/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext.xml b/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext.xml deleted file mode 100644 index faeaa22036..0000000000 --- a/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext.xml +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - my_run_as_password - - - - - - my_run_as_password - - - - my_password - - - - - - - - - - - - - - - - marissa=koala,ROLE_TELLER,ROLE_SUPERVISOR - dianne=emu,ROLE_TELLER - scott=wombat,ROLE_TELLER - peter=opal,disabled,ROLE_TELLER - - - - - - - - - - - - - - - Contacts Realm - - - - - - - - - - - - - - - false - - - - - - - - - - - - - - - - - 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 - - - - - - - - - - - - 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 - - - - - - - - sample.contact.ContactManager - - - - - - - - - - - - - - sample.contact.ContactManager - - - - - - - - - - - diff --git a/samples/contacts/src/main/webapp/ca/WEB-INF/web.xml b/samples/contacts/src/main/webapp/ca/WEB-INF/web.xml index 4994cbca72..e72ee88891 100644 --- a/samples/contacts/src/main/webapp/ca/WEB-INF/web.xml +++ b/samples/contacts/src/main/webapp/ca/WEB-INF/web.xml @@ -3,73 +3,65 @@ Contacts Sample Application - - Example of an application secured using Acegi Security System for Spring. - - contextConfigLocation - /WEB-INF/applicationContext.xml + + /WEB-INF/applicationContext-acegi-security.xml + /WEB-INF/applicationContext-common-business.xml + /WEB-INF/applicationContext-common-authorization.xml + + + + + log4jConfigLocation + /WEB-INF/classes/log4j.properties + - Acegi HTTP BASIC Authorization Filter + Acegi Security System for Spring HttpRequest Integration Filter net.sf.acegisecurity.util.FilterToBeanProxy targetClass - net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter - - - - - Acegi Security System for Spring Auto Integration Filter - net.sf.acegisecurity.util.FilterToBeanProxy - - targetClass - net.sf.acegisecurity.ui.AutoIntegrationFilter + net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter - Acegi HTTP BASIC Authorization Filter + Acegi Security System for Spring HttpRequest Integration Filter /* - - - Acegi Security System for Spring Auto Integration Filter - /* - - + org.springframework.web.context.ContextLoaderListener + + org.springframework.web.util.Log4jConfigListener + + contacts @@ -77,25 +69,20 @@ 1 + caucho org.springframework.web.servlet.DispatcherServlet 2 - contacts *.htm - caucho /caucho/* @@ -117,7 +104,7 @@ /secure/* - ROLE_TELLER + ROLE_USER ROLE_SUPERVISOR @@ -145,7 +132,7 @@ ROLE_SUPERVISOR - ROLE_TELLER + ROLE_USER diff --git a/samples/contacts/src/main/webapp/ca/login.jsp b/samples/contacts/src/main/webapp/ca/login.jsp index 9d952ce225..e2a2b82b50 100644 --- a/samples/contacts/src/main/webapp/ca/login.jsp +++ b/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 --%> Login @@ -8,11 +7,12 @@

Login

-

If you've used the standard springsecurity.xml, try these users: +

Valid users:

-

username marissa, password koala (granted ROLE_SUPERVISOR) -

username dianne, password emu (not a supervisor) -

username scott, password wombat (not a supervisor) +

username marissa, password koala +

username dianne, password emu +

username scott, password wombat +

username peter, password opal (user disabled)

<%-- this form-login-page form is also used as the diff --git a/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext.xml b/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml similarity index 52% rename from samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext.xml rename to samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml index 5a487bf25f..db9d6bdebe 100644 --- a/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext.xml +++ b/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml @@ -2,60 +2,40 @@ - - - - - my_run_as_password - + - - - - my_run_as_password - - - - - - + + + - - - + + + - - - - 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 - - - - - - - - + + + - - Contacts Realm - + + + + - + + Contacts Realm + + + @@ -77,7 +57,7 @@ - + @@ -88,86 +68,9 @@ false - - - - - - - - - - - false - - - - - - - - - - - - - - - - - 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 - - - - - - - - - - - - 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 - - - - - - - - sample.contact.ContactManager - - - - - - - - - - - - - - sample.contact.ContactManager - - - - - - - - - - + @@ -194,6 +97,11 @@ + + + + + /casfailed.jsp @@ -201,11 +109,6 @@ /j_acegi_cas_security_check - - - - - https://localhost:8443/cas/login @@ -215,7 +118,7 @@ false - + @@ -226,7 +129,6 @@ - CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON diff --git a/samples/contacts/src/main/webapp/cas/WEB-INF/web.xml b/samples/contacts/src/main/webapp/cas/WEB-INF/web.xml index 51c6fd479b..6299f90175 100644 --- a/samples/contacts/src/main/webapp/cas/WEB-INF/web.xml +++ b/samples/contacts/src/main/webapp/cas/WEB-INF/web.xml @@ -3,17 +3,33 @@ Contacts Sample Application - - Example of an application secured using Acegi Security System for Spring. - + + + contextConfigLocation + + /WEB-INF/applicationContext-acegi-security.xml + /WEB-INF/applicationContext-common-business.xml + /WEB-INF/applicationContext-common-authorization.xml + + + + + log4jConfigLocation + /WEB-INF/classes/log4j.properties + - - contextConfigLocation - /WEB-INF/applicationContext.xml - - Acegi Channel Processing Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -42,6 +49,7 @@ + Acegi CAS Processing Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -51,6 +59,7 @@ + Acegi HTTP BASIC Authorization Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -60,15 +69,21 @@ + - Acegi Security System for Spring Auto Integration Filter + Acegi Security System for Spring HttpSession Integration Filter net.sf.acegisecurity.util.FilterToBeanProxy targetClass - net.sf.acegisecurity.ui.AutoIntegrationFilter + net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter + Acegi HTTP Request Security Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -77,12 +92,12 @@ net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter - + Acegi Channel Processing Filter /* - + Acegi CAS Processing Filter /* @@ -94,7 +109,7 @@ - Acegi Security System for Spring Auto Integration Filter + Acegi Security System for Spring HttpSession Integration Filter /* @@ -104,23 +119,20 @@ org.springframework.web.context.ContextLoaderListener + + org.springframework.web.util.Log4jConfigListener + + contacts @@ -128,44 +140,25 @@ 1 + caucho org.springframework.web.servlet.DispatcherServlet 2 - - - - casproxy - edu.yale.its.tp.cas.proxy.ProxyTicketReceptor - 3 - - contacts *.htm - caucho /caucho/* - - casproxy - /casProxy/* - - index.jsp @@ -174,5 +167,5 @@ /spring /WEB-INF/spring.tld - + diff --git a/samples/contacts/src/main/webapp/cas/casfailed.jsp b/samples/contacts/src/main/webapp/cas/casfailed.jsp index da2f14cc15..8079e66687 100644 --- a/samples/contacts/src/main/webapp/cas/casfailed.jsp +++ b/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 --%> diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-authorization.xml b/samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-authorization.xml new file mode 100644 index 0000000000..5eba89d2ab --- /dev/null +++ b/samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-authorization.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + ACL_CONTACT_READ + sample.contact.Contact + + + + 1 + 2 + + + + + + + ACL_CONTACT_DELETE + sample.contact.Contact + + + + 1 + 16 + + + + + + + ACL_CONTACT_ADMIN + sample.contact.Contact + + + + 1 + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + + + + + + + + + + 1 + 2 + + + + + + + + + + + + + + + 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 + + + + + diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-business.xml b/samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-business.xml new file mode 100644 index 0000000000..aa8c2c90a7 --- /dev/null +++ b/samples/contacts/src/main/webapp/common/WEB-INF/applicationContext-common-business.xml @@ -0,0 +1,71 @@ + + + + + + + + + + org.hsqldb.jdbcDriver + + + jdbc:hsqldb:mem:contacts + + + sa + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + sample.contact.ContactManager + + + + + + + + + + + + + + diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml b/samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml index 1a7711f6d5..4ca259918d 100644 --- a/samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml +++ b/samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml @@ -3,6 +3,7 @@ @@ -22,6 +23,16 @@ + + + + + + + + + + @@ -29,11 +40,17 @@ secureAddForm secureIndexController secureDeleteController + adminPermissionController + deletePermissionController + addPermissionForm + + + true webContact @@ -46,6 +63,18 @@ + + true + addPermission + sample.contact.AddPermission + + addPermission + index.htm + + + + + /WEB-INF/jsp/ .jsp diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/addPermission.jsp b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/addPermission.jsp new file mode 100644 index 0000000000..96f7d76120 --- /dev/null +++ b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/addPermission.jsp @@ -0,0 +1,55 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + +Add Permission + +

Add Permission

+
+ + + + + + + + + + + + + + + + + + + +
Contact:
Recipient: + + + +
Permission: + + + +
+
+ + Please fix all errors! + +

+ +
+

+">Admin Permission ">Manage + + diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/adminPermission.jsp b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/adminPermission.jsp new file mode 100644 index 0000000000..81bddb07ba --- /dev/null +++ b/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" %> + + +Administer Permissions + +

Administer Permissions

+

+ + + +

+ + + + + + + + + +
+ + <% + SimpleAclEntry simpleAcl = ((SimpleAclEntry) pageContext.getAttribute("acl")); + String permissionBlock = simpleAcl.printPermissionsBlock(); + %> + <%= permissionBlock %> + [] + + + + + ">Del +
+

">Add Permission ">Manage + + diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/deletePermission.jsp b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/deletePermission.jsp new file mode 100644 index 0000000000..aa0c8eeee4 --- /dev/null +++ b/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" %> + + +Permission Deleted + +

Permission Deleted

+

+ + + +

+ + + +

">Manage + + diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/hello.jsp b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/hello.jsp index f005fe6a19..5b6cbf3e6a 100644 --- a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/hello.jsp +++ b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/hello.jsp @@ -4,28 +4,42 @@ Contacts Security Demo

Contacts Security Demo

-

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. +

Contacts demonstrates the following central Acegi Security capabilities: +

-

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. -

This application also demonstrates a public method, which is used to select -the random contact that is shown below: -

+

Please excuse the lack of look 'n' feel polish in this application. +It is about security, after all! :-) + +

To demonstrate a public method on ContactManager, +here's a random Contact: +

-

+

Get started by clicking "Manage"...

">Manage ">Debug diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp index 64c6da5645..c65fce0434 100644 --- a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp +++ b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp @@ -3,7 +3,7 @@ Your Contacts -

's Contacts

+

's Contacts

@@ -18,9 +18,12 @@ - - - + + + + + +
idNameEmail
">Del">Del">Admin Permission
diff --git a/samples/contacts/src/main/webapp/common/secure/debug.jsp b/samples/contacts/src/main/webapp/common/secure/debug.jsp index fb060eb19c..3e647615d0 100644 --- a/samples/contacts/src/main/webapp/common/secure/debug.jsp +++ b/samples/contacts/src/main/webapp/common/secure/debug.jsp @@ -27,21 +27,20 @@ if (context != null) { %> if (auth instanceof AuthByAdapter) { %>
SUCCESS! Your container adapter appears to be properly configured!

<% } else { %> -
SUCCESS! Your web filter appears to be properly configured!
+
SUCCESS! Your web filters appear to be properly configured!
<% } } else { %> Authentication object is null.
- This is an error and your container adapter will not operate properly until corrected.

+ This is an error and your Acegi Security application will not operate properly until corrected.

<% } } else { %> ContextHolder does not contain a SecureContext.
- This is an error and your container adapter will not operate properly until corrected.

+ This is an error and your Acegi Security application will not operate properly until corrected.

<% } } else { %> ContextHolder on ContextHolder is null.
- This indicates improper setup of the container adapter. Refer to the reference documentation.
- Also ensure the correct subclass of AbstractMvcIntegrationInterceptor is being used for your container.
+ This indicates improper setup of the Acegi Security application. Refer to the reference documentation.
<%} %> diff --git a/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext.xml b/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml similarity index 51% rename from samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext.xml rename to samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml index 638ff46053..277415095b 100644 --- a/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext.xml +++ b/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml @@ -2,54 +2,34 @@ - - - - - my_run_as_password - - - - - - my_run_as_password - + - - - - - - marissa=a564de63c2d0da68cf47586ee05984d7,ROLE_TELLER,ROLE_SUPERVISOR - dianne=65d15fe9156f9c4bbffd98085992a44e,ROLE_TELLER - scott=2b58af6dddbd072ed27ffc86725d7d3a,ROLE_TELLER - peter=22b5c9accc6e1ba628cedc63a72d57f8,disabled,ROLE_TELLER - - + + - + @@ -70,85 +50,7 @@ Contacts Realm - - - - - - - - - - - - - false - - - - - - - - - - - - - - - - - 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 - - - - - - - - - - - - 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 - - - - - - - - sample.contact.ContactManager - - - - - - - - - - - - - - sample.contact.ContactManager - - - - - - - - - + @@ -182,18 +84,18 @@ - - - /acegilogin.jsp?login_error=1 - / - /j_acegi_security_check - - + + + /acegilogin.jsp?login_error=1 + / + /j_acegi_security_check + + /acegilogin.jsp false @@ -203,7 +105,7 @@ false - + @@ -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 --> - + - 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 diff --git a/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml b/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml index 9e138212d4..ee12af5710 100644 --- a/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml +++ b/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml @@ -3,27 +3,35 @@ Contacts Sample Application - - Example of an application secured using Acegi Security System for Spring. - - contextConfigLocation - /WEB-INF/applicationContext.xml + + /WEB-INF/applicationContext-acegi-security.xml + /WEB-INF/applicationContext-common-business.xml + /WEB-INF/applicationContext-common-authorization.xml + + + log4jConfigLocation + /WEB-INF/classes/log4j.properties + + + Acegi Channel Processing Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -33,6 +41,7 @@ + Acegi Authentication Processing Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -42,6 +51,7 @@ + Acegi HTTP BASIC Authorization Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -51,15 +61,21 @@ + - Acegi Security System for Spring Auto Integration Filter + Acegi Security System for Spring HttpSession Integration Filter net.sf.acegisecurity.util.FilterToBeanProxy targetClass - net.sf.acegisecurity.ui.AutoIntegrationFilter + net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter + Acegi HTTP Request Security Filter net.sf.acegisecurity.util.FilterToBeanProxy @@ -89,7 +105,7 @@ - Acegi Security System for Spring Auto Integration Filter + Acegi Security System for Spring HttpSession Integration Filter /* @@ -99,23 +115,20 @@ org.springframework.web.context.ContextLoaderListener + + + org.springframework.web.util.Log4jConfigListener + contacts @@ -123,25 +136,20 @@ 1 + caucho org.springframework.web.servlet.DispatcherServlet 2 - contacts *.htm - caucho /caucho/* @@ -155,5 +163,5 @@ /spring /WEB-INF/spring.tld - + diff --git a/samples/contacts/src/main/webapp/filter/acegilogin.jsp b/samples/contacts/src/main/webapp/filter/acegilogin.jsp index 53010c8f9c..0cfe866dd7 100644 --- a/samples/contacts/src/main/webapp/filter/acegilogin.jsp +++ b/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 --%> @@ -11,11 +10,12 @@

Login

-

If you've used the standard springsecurity.xml, try these users: +

Valid users:

-

username marissa, password koala (granted ROLE_SUPERVISOR) -

username dianne, password emu (not a supervisor) -

username scott, password wombat (not a supervisor) +

username marissa, password koala +

username dianne, password emu +

username scott, password wombat +

username peter, password opal (user disabled)

<%-- this form-login-page form is also used as the