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.
- *
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
.
- *
- * 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.
- *
- * 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 @@ + + + + + +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 @@
+
+
+
+
+ ">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 @@
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
+ 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 @@
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
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Add Permission
+
+Administer Permissions
+
+
+
+
+
+
+
+
+
+ <%
+ SimpleAclEntry simpleAcl = ((SimpleAclEntry) pageContext.getAttribute("acl"));
+ String permissionBlock = simpleAcl.printPermissionsBlock();
+ %>
+ <%= permissionBlock %>
+ [
+
+
+
+ Permission Deleted
+
+
+
+
+Contacts Security Demo
-
+
-Contact
, the
+ main domain object in the application, has an access control list (ACL)
+ that indicates who is allowed read, administer and delete the object.ContactManager
service
+ layer bean has a number of secured (protected) and public (unprotected)
+ methods./secure
URI path is protected
+ by Acegi Security from principals not holding the
+ ROLE_USER
granted authority.ContactManager
,
+here's a random Contact
:
+
-
+
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) { %>
@@ -18,9 +18,12 @@
id Name Email
-
-
+
+
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 @@
-
-
-
- Login
-