SEC-1023: Add hasPermission() support to SecurityExpressionRoot

http://jira.springframework.org/browse/SEC-1023.

hasPermission() now delegates to a PermissionEvaluator interface, with a default implementation provided by the Acl module. The contacts sample now uses expressions on the ContactManager interface. The permission-evaluator element on global-method-security can be used to set the instance to an AclPermissionEvaluator. If not set, all hasPermission() expressions will evaluate to 'false'.
This commit is contained in:
Luke Taylor
2008-11-10 04:27:25 +00:00
parent fa6f57e3dd
commit e11114ce77
34 changed files with 392 additions and 357 deletions
@@ -14,7 +14,7 @@
*/
package sample.contact;
import org.springframework.security.acl.basic.SimpleAclEntry;
import org.springframework.security.acls.domain.BasePermission;
/**
@@ -27,7 +27,7 @@ public class AddPermission {
//~ Instance fields ================================================================================================
public Contact contact;
public Integer permission = new Integer(SimpleAclEntry.READ);
public Integer permission = BasePermission.READ.getMask();
public String recipient;
//~ Methods ========================================================================================================
@@ -26,7 +26,7 @@ import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.web.bind.RequestUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.view.RedirectView;
@@ -67,7 +67,7 @@ public class AddPermissionController extends SimpleFormController implements Ini
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
int contactId = RequestUtils.getRequiredIntParameter(request, "contactId");
int contactId = ServletRequestUtils.getRequiredIntParameter(request, "contactId");
Contact contact = contactManager.getById(new Long(contactId));
@@ -22,7 +22,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.web.bind.RequestUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
@@ -57,7 +57,7 @@ public class AdminPermissionController implements Controller, InitializingBean {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int id = RequestUtils.getRequiredIntParameter(request, "contactId");
int id = ServletRequestUtils.getRequiredIntParameter(request, "contactId");
Contact contact = contactManager.getById(new Long(id));
Acl acl = aclService.readAclById(new ObjectIdentityImpl(contact));
@@ -36,8 +36,9 @@ import java.util.Map;
/**
* Demonstrates accessing the {@link ContactManager} via remoting protocols.<P>Based on Spring's JPetStore sample,
* written by Juergen Hoeller.</p>
* Demonstrates accessing the {@link ContactManager} via remoting protocols.
* <p>
* Based on Spring's JPetStore sample, written by Juergen Hoeller.
*
* @author Ben Alex
*/
@@ -43,8 +43,6 @@ public class Contact implements Serializable {
//~ Methods ========================================================================================================
/**
* DOCUMENT ME!
*
* @return Returns the email.
*/
public String getEmail() {
@@ -52,8 +50,6 @@ public class Contact implements Serializable {
}
/**
* DOCUMENT ME!
*
* @return Returns the id.
*/
public Long getId() {
@@ -61,8 +57,6 @@ public class Contact implements Serializable {
}
/**
* DOCUMENT ME!
*
* @return Returns the name.
*/
public String getName() {
@@ -70,8 +64,6 @@ public class Contact implements Serializable {
}
/**
* DOCUMENT ME!
*
* @param email The email to set.
*/
public void setEmail(String email) {
@@ -83,8 +75,6 @@ public class Contact implements Serializable {
}
/**
* DOCUMENT ME!
*
* @param name The name to set.
*/
public void setName(String name) {
@@ -16,6 +16,8 @@ package sample.contact;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.expression.annotation.PostFilter;
import org.springframework.security.expression.annotation.PreAuthorize;
import java.util.List;
@@ -28,19 +30,28 @@ import java.util.List;
*/
public interface ContactManager {
//~ Methods ========================================================================================================
@PreAuthorize("hasPermission(#contact, admin)")
public void addPermission(Contact contact, Sid recipient, Permission permission);
public void create(Contact contact);
public void delete(Contact contact);
@PreAuthorize("hasPermission(#contact, admin)")
public void deletePermission(Contact contact, Sid recipient, Permission permission);
@PreAuthorize("hasRole('ROLE_USER')")
public void create(Contact contact);
@PreAuthorize("hasPermission(#contact, 'delete') or hasPermission(#contact, admin)")
public void delete(Contact contact);
@PreAuthorize("hasRole('ROLE_USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
public List getAll();
@PreAuthorize("hasRole('ROLE_USER')")
public List getAllRecipients();
@PreAuthorize(
"hasPermission(#id, 'sample.contact.Contact', read) or " +
"hasPermission(#id, 'sample.contact.Contact', admin)")
public Contact getById(Long id);
public Contact getRandomContact();
@@ -30,6 +30,7 @@ import org.springframework.security.acls.sid.Sid;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.InitializingBean;
@@ -47,6 +48,7 @@ import java.util.Random;
* @author Ben Alex
* @version $Id$
*/
@Transactional
public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
//~ Instance fields ================================================================================================
@@ -124,6 +126,7 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
}
}
@Transactional(readOnly=true)
public List getAll() {
if (logger.isDebugEnabled()) {
logger.debug("Returning all contacts");
@@ -132,6 +135,7 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
return contactDao.findAll();
}
@Transactional(readOnly=true)
public List getAllRecipients() {
if (logger.isDebugEnabled()) {
logger.debug("Returning all recipients");
@@ -142,6 +146,7 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
return list;
}
@Transactional(readOnly=true)
public Contact getById(Long id) {
if (logger.isDebugEnabled()) {
logger.debug("Returning contact with id: " + id);
@@ -152,9 +157,8 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
/**
* This is a public method.
*
* @return DOCUMENT ME!
*/
@Transactional(readOnly=true)
public Contact getRandomContact() {
if (logger.isDebugEnabled()) {
logger.debug("Returning random contact");
@@ -24,7 +24,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.web.bind.RequestUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
@@ -60,9 +60,9 @@ public class DeletePermissionController implements Controller, InitializingBean
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// <c:param name="sid" value="${acl.sid.principal}"/><c:param name="permission" value="${acl.permission.mask}"/></c:url>">Del</A>
int contactId = RequestUtils.getRequiredIntParameter(request, "contactId");
String sid = RequestUtils.getRequiredStringParameter(request, "sid");
int mask = RequestUtils.getRequiredIntParameter(request, "permission");
int contactId = ServletRequestUtils.getRequiredIntParameter(request, "contactId");
String sid = ServletRequestUtils.getRequiredStringParameter(request, "sid");
int mask = ServletRequestUtils.getRequiredIntParameter(request, "permission");
Contact contact = contactManager.getById(new Long(contactId));
@@ -51,7 +51,7 @@ public class PublicIndexController implements Controller, InitializingBean {
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
throws ServletException, IOException {
Contact rnd = contactManager.getRandomContact();
return new ModelAndView("hello", "contact", rnd);
@@ -17,6 +17,11 @@ package sample.contact;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.Authentication;
import org.springframework.security.acls.Permission;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.expression.PermissionEvaluator;
import org.springframework.util.Assert;
import org.springframework.web.servlet.ModelAndView;
@@ -35,38 +40,55 @@ import javax.servlet.http.HttpServletResponse;
/**
* Controller for secure index page.
* <p>
* This controller displays a list of all the contacts for which the current user has read or admin permissions.
* It makes a call to {@link ContactManager#getAll()} which automatically filters the returned list using Spring
* Security's ACL mechanism (see the expression annotations on this interface for the details).
* <p>
* In addition to rendering the list of contacts, the view will also include a "Del" or "Admin" link beside the
* contact, depending on whether the user has the corresponding permissions (admin permission is assumed to imply
* delete here). This information is stored in the model using the injected {@link PermissionEvaluator} instance.
* The implementation should be an instance of {@link AclPermissionEvaluator} or one which is compatible with Spring
* Security's ACL module.
*
* @author Ben Alex
* @version $Id$
*/
public class SecureIndexController implements Controller, InitializingBean {
private final static Permission[] HAS_DELETE = new Permission[] {BasePermission.DELETE, BasePermission.ADMINISTRATION};
private final static Permission[] HAS_ADMIN = new Permission[] {BasePermission.ADMINISTRATION};
//~ Instance fields ================================================================================================
private ContactManager contactManager;
private PermissionEvaluator permissionEvaluator;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(contactManager, "A ContactManager implementation is required");
}
public ContactManager getContactManager() {
return contactManager;
Assert.notNull(permissionEvaluator, "A PermissionEvaluator implementation is required");
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
List myContactsList = contactManager.getAll();
Contact[] myContacts;
throws ServletException, IOException {
List<Contact> myContactsList = contactManager.getAll();
Map<Contact,Boolean> hasDelete = new HashMap<Contact,Boolean>(myContactsList.size());
Map<Contact,Boolean> hasAdmin = new HashMap<Contact,Boolean>(myContactsList.size());
if (myContactsList.size() == 0) {
myContacts = null;
} else {
myContacts = (Contact[]) myContactsList.toArray(new Contact[] {});
Authentication user = SecurityContextHolder.getContext().getAuthentication();
for (Contact contact : myContactsList) {
hasDelete.put(contact,
permissionEvaluator.hasPermission(user, contact, HAS_DELETE) ? Boolean.TRUE : Boolean.FALSE);
hasAdmin.put(contact,
permissionEvaluator.hasPermission(user, contact, HAS_ADMIN) ? Boolean.TRUE : Boolean.FALSE);
}
Map model = new HashMap();
model.put("contacts", myContacts);
model.put("contacts", myContactsList);
model.put("hasDeletePermission", hasDelete);
model.put("hasAdminPermission", hasAdmin);
return new ModelAndView("index", "model", model);
}
@@ -74,4 +96,8 @@ public class SecureIndexController implements Controller, InitializingBean {
public void setContactManager(ContactManager contact) {
this.contactManager = contact;
}
public void setPermissionEvaluator(PermissionEvaluator pe) {
this.permissionEvaluator = pe;
}
}