diff --git a/client/client.properties b/client/client.properties new file mode 100644 index 0000000..9f3feee --- /dev/null +++ b/client/client.properties @@ -0,0 +1,8 @@ +# Properties file with server URL settings for remote access. +# Applied by PropertyPlaceholderConfigurer from "clientContext.xml". +# + +serverName=localhost +httpPort=8080 +contextPath=/spring-security-sample-contacts-filter +rmiPort=1099 diff --git a/client/clientContext.xml b/client/clientContext.xml new file mode 100644 index 0000000..d14785d --- /dev/null +++ b/client/clientContext.xml @@ -0,0 +1,73 @@ + + + + + + + + + + client.properties + + + + + + + + + + sample.contact.ContactManager + + + http://${serverName}:${httpPort}${contextPath}/remoting/ContactManager-httpinvoker + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a01bd0d --- /dev/null +++ b/pom.xml @@ -0,0 +1,267 @@ + + + 4.0.0 + org.springframework.security + spring-security-samples-contacts-xml + 3.2.0.RELEASE + war + spring-security-samples-contacts-xml + spring-security-samples-contacts-xml + http://spring.io/spring-security + + spring.io + http://spring.io/ + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + rwinch + Rob Winch + rwinch@gopivotal.com + + + + scm:git:git://github.com/spring-projects/spring-security + scm:git:git://github.com/spring-projects/spring-security + https://github.com/spring-projects/spring-security + + + + + maven-compiler-plugin + + 1.6 + 1.6 + + + + maven-war-plugin + 2.3 + + false + + + + + + + + + + commons-dbcp + commons-dbcp + 20030825.184428 + jar + compile + + + commons-pool + commons-pool + 20030825.183949 + jar + compile + + + + + + org.hibernate + hibernate-entitymanager + 3.6.0.Final + compile + + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + 1.0.0.Final + compile + + + + org.apache.tiles + tiles-jsp + 2.2.2 + + + + org.springframework.security + spring-security-acl + 3.2.0.RELEASE + compile + + + org.springframework.security + spring-security-core + 3.2.0.RELEASE + compile + + + org.springframework + spring-aop + 3.2.6.RELEASE + compile + + + org.springframework + spring-beans + 3.2.6.RELEASE + compile + + + org.springframework + spring-orm + 3.2.6.RELEASE + jar + compile + + + org.springframework + spring-context + 3.2.6.RELEASE + compile + + + org.springframework + spring-core + 3.2.6.RELEASE + compile + + + commons-logging + commons-logging + + + + + org.springframework + spring-jdbc + 3.2.6.RELEASE + compile + + + org.springframework + spring-tx + 3.2.6.RELEASE + compile + + + org.springframework + spring-web + 3.2.6.RELEASE + compile + + + org.springframework + spring-webmvc + 3.2.6.RELEASE + compile + + + commons-logging + commons-logging + 1.1.1 + compile + true + + + org.apache.tomcat + tomcat-servlet-api + 7.0.33 + provided + + + ch.qos.logback + logback-classic + 0.9.29 + runtime + + + javax.servlet + jstl + 1.2 + runtime + + + net.sf.ehcache + ehcache + 1.6.2 + runtime + + + org.hsqldb + hsqldb + 2.3.1 + runtime + + + org.slf4j + jcl-over-slf4j + 1.7.5 + runtime + + + org.springframework.security + spring-security-config + 3.2.0.RELEASE + runtime + + + org.springframework.security + spring-security-taglibs + 3.2.0.RELEASE + runtime + + + org.springframework.security + spring-security-web + 3.2.0.RELEASE + runtime + + + org.springframework + spring-context-support + 3.2.6.RELEASE + runtime + + + junit + junit + 4.10 + test + + + org.easytesting + fest-assert + 1.4 + test + + + org.mockito + mockito-core + 1.9.5 + test + + + org.springframework + spring-test + 3.2.6.RELEASE + test + + + org.apache.derby + derbyclient + 10.10.1.1 + + + + /sample + + diff --git a/src/main/java/org/springframework/security/samples/contacts/client/ClientApplication.java b/src/main/java/org/springframework/security/samples/contacts/client/ClientApplication.java new file mode 100644 index 0000000..d77b120 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/client/ClientApplication.java @@ -0,0 +1,142 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.samples.contacts.client; + + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.context.support.FileSystemXmlApplicationContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.security.samples.contacts.service.ContactManager; +import org.springframework.util.StopWatch; + + +/** + * Demonstrates accessing the {@link ContactManager} via remoting protocols. + *

+ * Based on Spring's JPetStore sample, written by Juergen Hoeller. + * + * @author Ben Alex + */ +public class ClientApplication { + //~ Instance fields ================================================================================================ + + private final ListableBeanFactory beanFactory; + + //~ Constructors =================================================================================================== + + public ClientApplication(ListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + //~ Methods ======================================================================================================== + + public void invokeContactManager(Authentication authentication, int nrOfCalls) { + StopWatch stopWatch = new StopWatch(nrOfCalls + " ContactManager call(s)"); + Map contactServices = this.beanFactory.getBeansOfType(ContactManager.class, true, true); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + for (String beanName : contactServices.keySet()) { + Object object = this.beanFactory.getBean("&" + beanName); + + try { + System.out.println("Trying to find setUsername(String) method on: " + object.getClass().getName()); + + Method method = object.getClass().getMethod("setUsername", new Class[] {String.class}); + System.out.println("Found; Trying to setUsername(String) to " + authentication.getPrincipal()); + method.invoke(object, authentication.getPrincipal()); + } catch (NoSuchMethodException ignored) { + System.out.println("This client proxy factory does not have a setUsername(String) method"); + } catch (IllegalAccessException ignored) { + ignored.printStackTrace(); + } catch (InvocationTargetException ignored) { + ignored.printStackTrace(); + } + + try { + System.out.println("Trying to find setPassword(String) method on: " + object.getClass().getName()); + + Method method = object.getClass().getMethod("setPassword", new Class[] {String.class}); + method.invoke(object, authentication.getCredentials()); + System.out.println("Found; Trying to setPassword(String) to " + authentication.getCredentials()); + } catch (NoSuchMethodException ignored) { + System.out.println("This client proxy factory does not have a setPassword(String) method"); + } catch (IllegalAccessException ignored) {} + catch (InvocationTargetException ignored) {} + + ContactManager remoteContactManager = contactServices.get(beanName); + System.out.println("Calling ContactManager '" + beanName + "'"); + + stopWatch.start(beanName); + + List contacts = null; + + for (int i = 0; i < nrOfCalls; i++) { + contacts = remoteContactManager.getAll(); + } + + stopWatch.stop(); + + if (contacts.size() != 0) { + for(Contact contact : contacts) { + System.out.println("Contact: " + contact); + } + } else { + System.out.println("No contacts found which this user has permission to"); + } + + System.out.println(); + System.out.println(stopWatch.prettyPrint()); + } + + SecurityContextHolder.clearContext(); + } + + public static void main(String[] args) { + String username = System.getProperty("username", ""); + String password = System.getProperty("password", ""); + String nrOfCallsString = System.getProperty("nrOfCalls", ""); + + if ("".equals(username) || "".equals(password)) { + System.out.println( + "You need to specify the user ID to use, the password to use, and optionally a number of calls " + + "using the username, password, and nrOfCalls system properties respectively. eg for user rod, " + + "use: -Dusername=rod -Dpassword=koala' for a single call per service and " + + "use: -Dusername=rod -Dpassword=koala -DnrOfCalls=10 for ten calls per service."); + System.exit(-1); + } else { + int nrOfCalls = 1; + + if (!"".equals(nrOfCallsString)) { + nrOfCalls = Integer.parseInt(nrOfCallsString); + } + + ListableBeanFactory beanFactory = new FileSystemXmlApplicationContext("clientContext.xml"); + ClientApplication client = new ClientApplication(beanFactory); + + client.invokeContactManager(new UsernamePasswordAuthenticationToken(username, password), nrOfCalls); + System.exit(0); + } + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/dao/AclDao.java b/src/main/java/org/springframework/security/samples/contacts/dao/AclDao.java new file mode 100644 index 0000000..683df0a --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/dao/AclDao.java @@ -0,0 +1,38 @@ +package org.springframework.security.samples.contacts.dao; + +import java.io.Serializable; +import java.util.List; + +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.samples.contacts.entity.AclClass; +import org.springframework.security.samples.contacts.entity.AclEntry; +import org.springframework.security.samples.contacts.entity.AclObjectIdentity; +import org.springframework.security.samples.contacts.entity.AclSid; + +public interface AclDao { + + List findChildren(Serializable identifier, String type); + + AclObjectIdentity getObjectIdentity(String type, Serializable identifier); + + void createObjectIdentity(AclObjectIdentity identity); + + List findAclSidList(Boolean valueOf, String sidName); + + AclSid createAclSid(AclSid sid2); + + List findAclClassList(String type); + + AclClass createAclClass(AclClass clazz); + + void deleteEntries(AclObjectIdentity oidPrimaryKey); + + void deleteObjectIdentity(AclObjectIdentity oidPrimaryKey); + + void createEntries(List entries); + + boolean updateObjectIdentity(AclObjectIdentity aclObject); + + AclSid findAclSid(String principal); + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/dao/ContactDao.java b/src/main/java/org/springframework/security/samples/contacts/dao/ContactDao.java new file mode 100644 index 0000000..8ceb1ab --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/dao/ContactDao.java @@ -0,0 +1,49 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.samples.contacts.dao; + +import java.util.List; + +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.stereotype.Repository; + + + +/** + * Provides access to the application's persistence layer. + * + * @author Ben Alex + */ +@Repository +public interface ContactDao { + //~ Methods ======================================================================================================== + + public Contact create(Contact contact); + + public void delete(Long contactId); + + public List findAll(); + + public List findAllPrincipals(); + + public List findAllRoles(); + + public Contact getById(Long id); + + public void update(Contact contact); + + public void delete(Contact contact); +} diff --git a/src/main/java/org/springframework/security/samples/contacts/dao/UserDao.java b/src/main/java/org/springframework/security/samples/contacts/dao/UserDao.java new file mode 100644 index 0000000..f727f57 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/dao/UserDao.java @@ -0,0 +1,11 @@ +package org.springframework.security.samples.contacts.dao; + +import org.springframework.security.samples.contacts.entity.User; + + + +public interface UserDao { + + User findByUsername(String username); + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/dao/impl/AclDaoJpa.java b/src/main/java/org/springframework/security/samples/contacts/dao/impl/AclDaoJpa.java new file mode 100644 index 0000000..0fd4e09 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/dao/impl/AclDaoJpa.java @@ -0,0 +1,128 @@ +package org.springframework.security.samples.contacts.dao.impl; + +import java.io.Serializable; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.samples.contacts.dao.AclDao; +import org.springframework.security.samples.contacts.entity.AclClass; +import org.springframework.security.samples.contacts.entity.AclEntry; +import org.springframework.security.samples.contacts.entity.AclObjectIdentity; +import org.springframework.security.samples.contacts.entity.AclSid; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Repository +public class AclDaoJpa implements AclDao { + + + @PersistenceContext + private EntityManager entityManager; + + @Override + public List findChildren(Serializable identifier, String type) { + Query query = entityManager.createQuery("select aoi from AclObjectIdentity aoi, AclObjectIdentity parent, AclClass aclClass where aoi.parentObject = parent and aoi.objIdClass = aclClass and parent.objIdIdentity = :objIdIdentity and parent.objIdClass = (select acl FROM AclClass acl where acl.clazz = :clazz)"); + query.setParameter("objIdIdentity", identifier); + query.setParameter("clazz", type); + + return query.getResultList(); + } + + @Override + public AclObjectIdentity getObjectIdentity(String type, Serializable identifier) { + Query query = entityManager.createQuery("select aoi from AclObjectIdentity aoi, AclClass aclClass where aoi.objIdIdentity = :objIdIdentity and aoi.objIdClass = aclClass and aclClass.clazz = :clazz)"); + query.setParameter("objIdIdentity", identifier); + query.setParameter("clazz", type); + + return (AclObjectIdentity) query.getSingleResult(); + } + + @Override + @Transactional + public void createObjectIdentity(AclObjectIdentity identity) { + // TODO Auto-generated method stub + entityManager.persist(identity); + } + + @Override + public List findAclSidList(Boolean principal, String sidName) { + Query query = entityManager.createQuery("select sid from AclSid sid where sid.principal=:principal and sid.sid=:sid"); + query.setParameter("principal", principal); + query.setParameter("sid", sidName); + return query.getResultList(); + } + + @Override + @Transactional + public AclSid createAclSid(AclSid sid2) { + entityManager.persist(sid2); + return sid2; + } + + @Override + public List findAclClassList(String type) { + Query query = entityManager.createQuery("select clazz from AclClass clazz where clazz.clazz=:clazz"); + + query.setParameter("clazz", type); + return query.getResultList(); + } + + @Override + @Transactional + public AclClass createAclClass(AclClass clazz) { + entityManager.persist(clazz); + return clazz; + } + + @Override + @Transactional + public void deleteEntries(AclObjectIdentity objectIdentity) { + objectIdentity = entityManager.find(AclObjectIdentity.class, objectIdentity.getId()); + if(objectIdentity.getEntries()!=null) { + for(AccessControlEntry entry:objectIdentity.getEntries()) { + entityManager.remove((AclEntry)entry); + } + } + + } + + @Override + public void deleteObjectIdentity(AclObjectIdentity oidPrimaryKey) { + // TODO Auto-generated method stub + entityManager.remove(oidPrimaryKey); + } + + @Override + @Transactional + public void createEntries(List entries) { + for(AclEntry entry:entries) { + entityManager.persist(entry); + } + + } + + @Override + public boolean updateObjectIdentity(AclObjectIdentity aclObject) { + // TODO Auto-generated method stub + entityManager.merge(aclObject); + return true; + } + + @Override + public AclSid findAclSid(String principal) { + Query query = entityManager.createQuery("select sid from AclSid sid where sid.sid=:sid"); + + query.setParameter("sid", principal); + List results = query.getResultList(); + if(results.size()>0) { + return results.get(0); + } + return null; + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/dao/impl/ContactDaoJpa.java b/src/main/java/org/springframework/security/samples/contacts/dao/impl/ContactDaoJpa.java new file mode 100644 index 0000000..10f4762 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/dao/impl/ContactDaoJpa.java @@ -0,0 +1,76 @@ +package org.springframework.security.samples.contacts.dao.impl; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +import org.springframework.security.samples.contacts.dao.ContactDao; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Repository +public class ContactDaoJpa implements ContactDao { + + @PersistenceContext + private EntityManager entityManager; + + + @Override + @Transactional + public Contact create(Contact contact) { + // TODO Auto-generated method stub + entityManager.persist(contact); + return contact; + } + + @Override + @Transactional + public void delete(Long contactId) { + // TODO Auto-generated method stub + Contact contact = entityManager.find(Contact.class, contactId); + entityManager.remove(contact); + } + + @Override + public List findAll() { + Query query = entityManager.createQuery("select u from Contact u"); + + return query.getResultList(); + } + + @Override + public List findAllPrincipals() { + Query query = entityManager.createQuery("select u.username from User u", String.class); + return query.getResultList(); + } + + @Override + public List findAllRoles() { + Query query = entityManager.createQuery("select u.name from Role u", String.class); + return query.getResultList(); + } + + @Override + public Contact getById(Long id) { + + return entityManager.find(Contact.class, id); + } + + @Override + @Transactional + public void update(Contact contact) { + // TODO Auto-generated method stub + entityManager.merge(contact); + } + + @Override + @Transactional + public void delete(Contact contact) { + // TODO Auto-generated method stub + entityManager.remove(contact); + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/dao/impl/UserDaoJpa.java b/src/main/java/org/springframework/security/samples/contacts/dao/impl/UserDaoJpa.java new file mode 100644 index 0000000..9749763 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/dao/impl/UserDaoJpa.java @@ -0,0 +1,24 @@ +package org.springframework.security.samples.contacts.dao.impl; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +import org.springframework.security.samples.contacts.dao.UserDao; +import org.springframework.security.samples.contacts.entity.User; +import org.springframework.stereotype.Repository; + +@Repository +public class UserDaoJpa implements UserDao { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public User findByUsername(String username) { + Query query = entityManager.createQuery("select u from User u where u.username=:username"); + query.setParameter("username", username); + return (User) query.getSingleResult(); + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/AclClass.java b/src/main/java/org/springframework/security/samples/contacts/entity/AclClass.java new file mode 100644 index 0000000..ceef7ac --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/AclClass.java @@ -0,0 +1,31 @@ +package org.springframework.security.samples.contacts.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="ACL_CLASS") +public class AclClass { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + @Column(name="CLASS",unique=true) + private String clazz; + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public String getClazz() { + return clazz; + } + public void setClazz(String clazz) { + this.clazz = clazz; + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/AclEntry.java b/src/main/java/org/springframework/security/samples/contacts/entity/AclEntry.java new file mode 100644 index 0000000..898c6cd --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/AclEntry.java @@ -0,0 +1,110 @@ +package org.springframework.security.samples.contacts.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.Acl; +import org.springframework.security.acls.model.Permission; + +@Entity +@Table(name="ACL_ENTRY") +public class AclEntry{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @ManyToOne + @JoinColumn(name="ACL_OBJECT_IDENTITY") + private AclObjectIdentity aclObjectIdentity; + + @Column(name="ACE_ORDER") + private Integer aceOrder; + + @ManyToOne + @JoinColumn(name="SID") + private AclSid sid; + + @Column(name="MASK") + private Integer mask; + + @Column(name="GRANTING") + private Boolean granting; + + @Column(name="AUDIT_SUCCESS") + private Boolean auditSuccess; + + @Column(name="AUDIT_FAILURE") + private Boolean auditFailure; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public AclObjectIdentity getAclObjectIdentity() { + return aclObjectIdentity; + } + + public void setAclObjectIdentity(AclObjectIdentity aclObjectIdentity) { + this.aclObjectIdentity = aclObjectIdentity; + } + + public Integer getAceOrder() { + return aceOrder; + } + + public void setAceOrder(Integer aceOrder) { + this.aceOrder = aceOrder; + } + + public AclSid getSid() { + return sid; + } + + public void setSid(AclSid sid) { + this.sid = sid; + } + + public Integer getMask() { + return mask; + } + + public void setMask(Integer mask) { + this.mask = mask; + } + + public Boolean getGranting() { + return granting; + } + + public void setGranting(Boolean granting) { + this.granting = granting; + } + + public Boolean getAuditSuccess() { + return auditSuccess; + } + + public void setAuditSuccess(Boolean auditSuccess) { + this.auditSuccess = auditSuccess; + } + + public Boolean getAuditFailure() { + return auditFailure; + } + + public void setAuditFailure(Boolean auditFailure) { + this.auditFailure = auditFailure; + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/AclObjectIdentity.java b/src/main/java/org/springframework/security/samples/contacts/entity/AclObjectIdentity.java new file mode 100644 index 0000000..50f248a --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/AclObjectIdentity.java @@ -0,0 +1,133 @@ +package org.springframework.security.samples.contacts.entity; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.Acl; +import org.springframework.security.acls.model.MutableAcl; +import org.springframework.security.acls.model.NotFoundException; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.acls.model.UnloadedSidException; + +@Entity +@Table(name="ACL_OBJECT_IDENTITY") +public class AclObjectIdentity implements ObjectIdentity{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @ManyToOne + @JoinColumn(name="OBJECT_ID_CLASS") + private AclClass objIdClass; + + @Column(name="OBJECT_ID_IDENTITY") + private Long objIdIdentity; + + @ManyToOne + @JoinColumn(name="PARENT_OBJECT") + private AclObjectIdentity parentObject; + + @ManyToOne + @JoinColumn(name="OWNER_SID") + private AclSid owner; + + @Column(name="ENTRIES_INHERITING") + private Boolean entriesInheriting; + + @OneToMany(mappedBy="aclObjectIdentity") + private List aclEntries; + + public List getAclEntries() { + return aclEntries; + } + + public void setAclEntries(List aclEntries) { + this.aclEntries = aclEntries; + } + + public List getEntries() { + if(aclEntries!=null) { + return Arrays.asList(aclEntries.toArray(new AccessControlEntry[0])); + } + return null; + } + + public void setEntries(List entries) { + this.aclEntries = entries; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public AclClass getObjIdClass() { + return objIdClass; + } + + public void setObjIdClass(AclClass objIdClass) { + this.objIdClass = objIdClass; + } + + public Long getObjIdIdentity() { + return objIdIdentity; + } + + public void setObjIdIdentity(Long objIdIdentity) { + this.objIdIdentity = objIdIdentity; + } + + public AclObjectIdentity getParentObject() { + return parentObject; + } + + public void setParentObject(AclObjectIdentity parentObject) { + this.parentObject = parentObject; + } + + public AclSid getOwner() { + return owner; + } + + public void setOwner(AclSid owner) { + this.owner = owner; + } + + public Boolean getEntriesInheriting() { + return entriesInheriting; + } + + public void setEntriesInheriting(Boolean entriesInheriting) { + this.entriesInheriting = entriesInheriting; + } + + @Override + public Serializable getIdentifier() { + + return this.objIdIdentity; + } + + @Override + public String getType() { + + return objIdClass.getClazz(); + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/AclSid.java b/src/main/java/org/springframework/security/samples/contacts/entity/AclSid.java new file mode 100644 index 0000000..af744f7 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/AclSid.java @@ -0,0 +1,49 @@ +package org.springframework.security.samples.contacts.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.springframework.security.acls.model.Sid; + +@Entity +@Table(name="ACL_SID") +public class AclSid implements Sid{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column + private Boolean principal; + + @Column(name="SID") + private String sid; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Boolean getPrincipal() { + return principal; + } + + public void setPrincipal(Boolean principal) { + this.principal = principal; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/Contact.java b/src/main/java/org/springframework/security/samples/contacts/entity/Contact.java new file mode 100644 index 0000000..d2f347d --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/Contact.java @@ -0,0 +1,71 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.samples.contacts.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + + +/** + * Represents a contact. + * + * @author Ben Alex + */ +@Entity +@Table(name="CONTACTS") +public class Contact { + //~ Instance fields ================================================================================================ + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column + private String email; + + @Column(name="CONTACT_NAME") + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/Role.java b/src/main/java/org/springframework/security/samples/contacts/entity/Role.java new file mode 100644 index 0000000..0e6c735 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/Role.java @@ -0,0 +1,33 @@ +package org.springframework.security.samples.contacts.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(unique=true) + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/User.java b/src/main/java/org/springframework/security/samples/contacts/entity/User.java new file mode 100644 index 0000000..95f9cf4 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/User.java @@ -0,0 +1,75 @@ +package org.springframework.security.samples.contacts.entity; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +@Entity +@Table(name="USERS") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(unique=true) + private String username; + + @Column + private String password; + + @Column + private boolean enabled; + + @ManyToMany(fetch=FetchType.EAGER) + @JoinTable(name="USER_ROLES") + private List roles; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/entity/UserPermission.java b/src/main/java/org/springframework/security/samples/contacts/entity/UserPermission.java new file mode 100644 index 0000000..db82f82 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/entity/UserPermission.java @@ -0,0 +1,12 @@ +package org.springframework.security.samples.contacts.entity; + +import org.springframework.security.acls.domain.AbstractPermission; + +public class UserPermission extends AbstractPermission { + + protected UserPermission(int mask) { + super(mask); + // TODO Auto-generated constructor stub + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/service/ContactManager.java b/src/main/java/org/springframework/security/samples/contacts/service/ContactManager.java new file mode 100644 index 0000000..14cc756 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/service/ContactManager.java @@ -0,0 +1,58 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.samples.contacts.service; + +import java.util.List; + +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.samples.contacts.entity.Contact; + + +/** + * Interface for the application's services layer. + * + * @author Ben Alex + */ +public interface ContactManager { + //~ Methods ======================================================================================================== + @PreAuthorize("hasPermission(#contact, admin)") + public void addPermission(Contact contact, Sid recipient, Permission permission); + + @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, 'org.springframework.security.samples.contacts.entity.Contact', read) or " + +// "hasPermission(#id, 'org.springframework.security.samples.contacts.entity.Contact', admin)") + public Contact getById(Long id); + + public Contact getRandomContact(); +} diff --git a/src/main/java/org/springframework/security/samples/contacts/service/impl/ContactManagerBackend.java b/src/main/java/org/springframework/security/samples/contacts/service/impl/ContactManagerBackend.java new file mode 100644 index 0000000..49ab4ce --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/service/impl/ContactManagerBackend.java @@ -0,0 +1,184 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.samples.contacts.service.impl; + + +import java.util.List; +import java.util.Random; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.MutableAcl; +import org.springframework.security.acls.model.MutableAclService; +import org.springframework.security.acls.model.NotFoundException; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.samples.contacts.dao.ContactDao; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.security.samples.contacts.service.ContactManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +/** + * Concrete implementation of {@link ContactManager}. + * + * @author Ben Alex + */ + +public class ContactManagerBackend implements ContactManager { + //~ Instance fields ================================================================================================ + + + private ContactDao contactDao; + + + private MutableAclService mutableAclService; + private int counter = 1000; + + private static final Logger logger = LoggerFactory.getLogger(ContactManagerBackend.class); + + + public void addPermission(Contact contact, Sid recipient, Permission permission) { + MutableAcl acl; + ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId()); + + try { + acl = (MutableAcl) mutableAclService.readAclById(oid); + } catch (NotFoundException nfe) { + acl = mutableAclService.createAcl(oid); + } + + acl.insertAce(acl.getEntries().size(), permission, recipient, true); + mutableAclService.updateAcl(acl); + + logger.debug("Added permission " + permission + " for Sid " + recipient + " contact " + contact); + } + + public void create(Contact contact) { + // Create the Contact itself + contact.setId(new Long(counter++)); + contactDao.create(contact); + + // Grant the current principal administrative permission to the contact + addPermission(contact, new PrincipalSid(getUsername()), BasePermission.ADMINISTRATION); + + if (logger.isDebugEnabled()) { + logger.debug("Created contact " + contact + " and granted admin permission to recipient " + getUsername()); + } + } + + public void delete(Contact contact) { + contactDao.delete(contact.getId()); + + // Delete the ACL information as well + ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId()); + mutableAclService.deleteAcl(oid, false); + + if (logger.isDebugEnabled()) { + logger.debug("Deleted contact " + contact + " including ACL permissions"); + } + } + + public void deletePermission(Contact contact, Sid recipient, Permission permission) { + ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId()); + MutableAcl acl = (MutableAcl) mutableAclService.readAclById(oid); + + // Remove all permissions associated with this particular recipient (string equality to KISS) + List entries = acl.getEntries(); + + for (int i = 0; i < entries.size(); i++) { + if (entries.get(i).getSid().equals(recipient) && entries.get(i).getPermission().equals(permission)) { + acl.deleteAce(i); + } + } + + mutableAclService.updateAcl(acl); + + if (logger.isDebugEnabled()) { + logger.debug("Deleted contact " + contact + " ACL permissions for recipient " + recipient); + } + } + + @Transactional(readOnly=true) + public List getAll() { + logger.debug("Returning all contacts"); + + return contactDao.findAll(); + } + + @Transactional(readOnly=true) + public List getAllRecipients() { + logger.debug("Returning all recipients"); + + return contactDao.findAllPrincipals(); + } + + @Transactional(readOnly=true) + public Contact getById(Long id) { + if (logger.isDebugEnabled()) { + logger.debug("Returning contact with id: " + id); + } + + return contactDao.getById(id); + } + + /** + * This is a public method. + */ + @Transactional(readOnly=true) + public Contact getRandomContact() { + logger.debug("Returning random contact"); + + Random rnd = new Random(); + List contacts = contactDao.findAll(); + int getNumber = rnd.nextInt(contacts.size()); + + return contacts.get(getNumber); + } + + protected String getUsername() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + if (auth.getPrincipal() instanceof UserDetails) { + return ((UserDetails) auth.getPrincipal()).getUsername(); + } else { + return auth.getPrincipal().toString(); + } + } + + public void setContactDao(ContactDao contactDao) { + this.contactDao = contactDao; + } + + public void setMutableAclService(MutableAclService mutableAclService) { + this.mutableAclService = mutableAclService; + } + + public void update(Contact contact) { + contactDao.update(contact); + + logger.debug("Updated contact " + contact); + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/service/impl/ContactManagerService.java b/src/main/java/org/springframework/security/samples/contacts/service/impl/ContactManagerService.java new file mode 100644 index 0000000..d17b383 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/service/impl/ContactManagerService.java @@ -0,0 +1,99 @@ +package org.springframework.security.samples.contacts.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.acls.domain.AclImpl; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.MutableAclService; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.samples.contacts.dao.ContactDao; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.security.samples.contacts.service.ContactManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ContactManagerService implements ContactManager { + + @Autowired + private ContactDao contactDao; + + @Autowired + private MutableAclService mutableAclService; + + @Override + @PreAuthorize("hasPermission(#contact, admin)") + public void addPermission(Contact contact, Sid recipient, Permission permission) { + // TODO Auto-generated method stub + + } + + @Override + @PreAuthorize("hasPermission(#contact, admin)") + public void deletePermission(Contact contact, Sid recipient, Permission permission) { + // TODO Auto-generated method stub + + } + + @Override + @PreAuthorize("hasRole('ROLE_USER')") + @Transactional + public void create(Contact contact) { + // TODO Auto-generated method stub + contact = contactDao.create(contact); + + // Create acl_object_identity rows (and also acl_class rows as needed + + final ObjectIdentity objectIdentity = new ObjectIdentityImpl(Contact.class, contact.getId()); + mutableAclService.createAcl(objectIdentity); + + AclImpl acl = (AclImpl) mutableAclService.readAclById(new ObjectIdentityImpl(Contact.class, + contact.getId())); + acl.insertAce(acl.getEntries().size(), BasePermission.ADMINISTRATION, new PrincipalSid(SecurityContextHolder.getContext().getAuthentication().getName()), true); + + mutableAclService.updateAcl(acl); + + } + + @Override + @PreAuthorize("hasPermission(#contact, 'delete') or hasPermission(#contact, admin)") + public void delete(Contact contact) { + // TODO Auto-generated method stub + contactDao.delete(contact.getId()); + } + + @Override + @PreAuthorize("hasRole('ROLE_USER')") +// @PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)") + public List getAll() { + + return contactDao.findAll(); + } + + @Override + @PreAuthorize("hasRole('ROLE_USER')") + public List getAllRecipients() { + return contactDao.findAllPrincipals(); + } + + @Override +// @PreAuthorize("hasPermission(#id, 'org.springframework.security.samples.contacts.entity.Contact', read) or hasPermission(#id, 'org.springframework.security.samples.contacts.entity.Contact', admin)") + public Contact getById(Long id) { + // TODO Auto-generated method stub + return contactDao.getById(id); + } + + @Override + public Contact getRandomContact() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/org/springframework/security/samples/contacts/service/impl/CustomUserDetailsService.java b/src/main/java/org/springframework/security/samples/contacts/service/impl/CustomUserDetailsService.java new file mode 100644 index 0000000..9dfc61f --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/service/impl/CustomUserDetailsService.java @@ -0,0 +1,71 @@ +package org.springframework.security.samples.contacts.service.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.samples.contacts.dao.UserDao; +import org.springframework.security.samples.contacts.entity.Role; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * A custom {@link UserDetailsService} where user information + * is retrieved from a JPA repository + */ +@Service +@Transactional(readOnly = true) +public class CustomUserDetailsService implements UserDetailsService { + + @Autowired + private UserDao userRepository; + + /** + * Returns a populated {@link UserDetails} object. + * The username is first retrieved from the database and then mapped to + * a {@link UserDetails} object. + */ + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + try { + org.springframework.security.samples.contacts.entity.User domainUser = userRepository.findByUsername(username); + + boolean enabled = true; + boolean accountNonExpired = true; + boolean credentialsNonExpired = true; + boolean accountNonLocked = true; + + return new User( + domainUser.getUsername(), + domainUser.getPassword(), + domainUser.isEnabled(), + accountNonExpired, + credentialsNonExpired, + accountNonLocked, + getGrantedAuthorities(domainUser.getRoles())); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + + /** + * Wraps {@link String} roles to {@link SimpleGrantedAuthority} objects + * @param roles {@link String} of roles + * @return list of granted authorities + */ + public static List getGrantedAuthorities(List roles) { + List authorities = new ArrayList(); + for (Role role : roles) { + authorities.add(new SimpleGrantedAuthority(role.getName())); + } + return authorities; + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/service/impl/JpaMutableAclService.java b/src/main/java/org/springframework/security/samples/contacts/service/impl/JpaMutableAclService.java new file mode 100644 index 0000000..6dd3f26 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/service/impl/JpaMutableAclService.java @@ -0,0 +1,362 @@ +package org.springframework.security.samples.contacts.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import javax.persistence.NoResultException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.acls.domain.AccessControlEntryImpl; +import org.springframework.security.acls.domain.AclImpl; +import org.springframework.security.acls.domain.GrantedAuthoritySid; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.jdbc.LookupStrategy; +import org.springframework.security.acls.model.Acl; +import org.springframework.security.acls.model.AclCache; +import org.springframework.security.acls.model.AlreadyExistsException; +import org.springframework.security.acls.model.ChildrenExistException; +import org.springframework.security.acls.model.MutableAcl; +import org.springframework.security.acls.model.MutableAclService; +import org.springframework.security.acls.model.NotFoundException; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.samples.contacts.dao.AclDao; +import org.springframework.security.samples.contacts.entity.AclClass; +import org.springframework.security.samples.contacts.entity.AclEntry; +import org.springframework.security.samples.contacts.entity.AclObjectIdentity; +import org.springframework.security.samples.contacts.entity.AclSid; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +@Service("aclService") +public class JpaMutableAclService implements MutableAclService { + private boolean foreignKeysInDatabase = true; + + + @Autowired + private LookupStrategy lookupStrategy; + + @Autowired + private AclCache aclCache; + + @Autowired + private AclDao aclDao; + + @Override + public List findChildren(ObjectIdentity parentIdentity) { + + List objects = aclDao.findChildren(parentIdentity.getIdentifier(), parentIdentity.getType()); + + if (objects.size() == 0) { + return null; + } + + return objects; + } + + @Override + public Acl readAclById(ObjectIdentity object) throws NotFoundException { + return readAclById(object, null); + } + + @Override + public Acl readAclById(ObjectIdentity object, List sids) throws NotFoundException { + Map map = readAclsById(Arrays.asList(object), sids); + Assert.isTrue(map.containsKey(object), "There should have been an Acl entry for ObjectIdentity " + object); + + return (Acl) map.get(object); + } + + @Override + public Map readAclsById(List objects) throws NotFoundException { + return readAclsById(objects, null); + } + + @Override + public Map readAclsById(List objects, List sids) throws NotFoundException { + Map result = lookupStrategy.readAclsById(objects, sids); + + // Check every requested object identity was found (throw NotFoundException if needed) + for (ObjectIdentity oid : objects) { + if (!result.containsKey(oid)) { + throw new NotFoundException("Unable to find ACL information for object identity '" + oid + "'"); + } + } + + return result; + } + + @Override + public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException { + Assert.notNull(objectIdentity, "Object Identity required"); + + // Check this object identity hasn't already been persisted + if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) { + throw new AlreadyExistsException("Object identity '" + objectIdentity + "' already exists"); + } + + // Need to retrieve the current principal, in order to know who "owns" this ACL (can be changed later on) + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + PrincipalSid sid = new PrincipalSid(auth); + + // Create the acl_object_identity row + createObjectIdentity(objectIdentity, sid); + + // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc) + Acl acl = readAclById(objectIdentity); + Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned"); + + return (MutableAcl) acl; + } + + /** + * Retrieves the primary key from the acl_object_identity table for the passed ObjectIdentity. Unlike some + * other methods in this implementation, this method will NOT create a row (use {@link + * #createObjectIdentity(ObjectIdentity, Sid)} instead). + * + * @param oid to find + * + * @return the object identity or null if not found + */ + protected AclObjectIdentity retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) { + try { + return aclDao.getObjectIdentity(oid.getType(), oid.getIdentifier()); + } catch (NoResultException notFound) { + return null; + } + } + + /** + * Creates an entry in the acl_object_identity table for the passed ObjectIdentity. The Sid is also + * necessary, as acl_object_identity has defined the sid column as non-null. + * + * @param object to represent an acl_object_identity for + * @param owner for the SID column (will be created if there is no acl_sid entry for this particular Sid already) + * @return + */ + protected void createObjectIdentity(ObjectIdentity object, Sid owner) { + AclSid sid = createOrRetrieveSidPrimaryKey(owner, true); + AclClass clazz = createOrRetrieveClassPrimaryKey(object.getType(), true); + AclObjectIdentity identity = new AclObjectIdentity(); + identity.setObjIdClass(clazz); + identity.setObjIdIdentity((Long) object.getIdentifier()); + identity.setOwner(sid); + identity.setEntriesInheriting(Boolean.TRUE); + aclDao.createObjectIdentity(identity); + + } + + /** + * Retrieves the primary key from acl_sid, creating a new row if needed and the allowCreate property is + * true. + * + * @param sid to find or create + * @param allowCreate true if creation is permitted if not found + * + * @return the primary key or null if not found + * + * @throws IllegalArgumentException if the Sid is not a recognized implementation. + */ + protected AclSid createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { + Assert.notNull(sid, "Sid required"); + + String sidName; + boolean sidIsPrincipal = true; + + if (sid instanceof PrincipalSid) { + sidName = ((PrincipalSid) sid).getPrincipal(); + } else if (sid instanceof GrantedAuthoritySid) { + sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority(); + sidIsPrincipal = false; + } else { + throw new IllegalArgumentException("Unsupported implementation of Sid"); + } + + List sidIds = aclDao.findAclSidList(Boolean.valueOf(sidIsPrincipal), sidName); + + if (!sidIds.isEmpty()) { + return sidIds.get(0); + } + + if (allowCreate) { + AclSid sid2 = new AclSid(); + sid2.setSid(sidName); + sid2.setPrincipal(Boolean.valueOf(sidIsPrincipal)); + return aclDao.createAclSid(sid2); + } + + return null; + } + + /** + * Retrieves the primary key from {@code acl_class}, creating a new row if needed and the + * {@code allowCreate} property is {@code true}. + * + * @param type to find or create an entry for (often the fully-qualified class name) + * @param allowCreate true if creation is permitted if not found + * + * @return the primary key or null if not found + */ + protected AclClass createOrRetrieveClassPrimaryKey(String type, boolean allowCreate) { + List classIds = aclDao.findAclClassList(type); + + if (!classIds.isEmpty()) { + return classIds.get(0); + } + + if (allowCreate) { + AclClass clazz = new AclClass(); + clazz.setClazz(type); + + Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), + "Transaction must be running"); + return aclDao.createAclClass(clazz); + } + + return null; + } + + + @Override + public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException { + Assert.notNull(objectIdentity, "Object Identity required"); + Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier"); + + if (deleteChildren) { + List children = findChildren(objectIdentity); + if (children != null) { + for (ObjectIdentity child : children) { + deleteAcl(child, true); + } + } + } else { + if (!foreignKeysInDatabase) { + // We need to perform a manual verification for what a FK would normally do + // We generally don't do this, in the interests of deadlock management + List children = findChildren(objectIdentity); + if (children != null) { + throw new ChildrenExistException("Cannot delete '" + objectIdentity + "' (has " + children.size() + + " children)"); + } + } + } + + AclObjectIdentity oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity); + + // Delete this ACL's ACEs in the acl_entry table + aclDao.deleteEntries(oidPrimaryKey); + + // Delete this ACL's acl_object_identity row + aclDao.deleteObjectIdentity(oidPrimaryKey); + + // Clear the cache + aclCache.evictFromCache(objectIdentity); + } + + @Override + /** + * This implementation will simply delete all ACEs in the database and recreate them on each invocation of + * this method. A more comprehensive implementation might use dirty state checking, or more likely use ORM + * capabilities for create, update and delete operations of {@link MutableAcl}. + */ + public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException { + Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier"); + + // Delete this ACL's ACEs in the acl_entry table + aclDao.deleteEntries(retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity())); + + // Create this ACL's ACEs in the acl_entry table + createEntries(acl); + + // Change the mutable columns in acl_object_identity + updateObjectIdentity(acl); + + // Clear the cache, including children + clearCacheIncludingChildren(acl.getObjectIdentity()); + + // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc) + return (MutableAcl) readAclById(acl.getObjectIdentity()); + } + + + /** + * Creates a new row in acl_entry for every ACE defined in the passed MutableAcl object. + * + * @param acl containing the ACEs to insert + */ + protected void createEntries(final MutableAcl acl) { + if(acl.getEntries().isEmpty()) { + return; + } + AclImpl aclImpl = (AclImpl)acl; + ObjectIdentityImpl objIdentity = (ObjectIdentityImpl) aclImpl.getObjectIdentity(); + List entries = new ArrayList(); + for(int i=0;i children = findChildren(objectIdentity); + if (children != null) { + for (ObjectIdentity child : children) { + clearCacheIncludingChildren(child); + } + } + aclCache.evictFromCache(objectIdentity); + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/utils/DataSourcePopulator.java b/src/main/java/org/springframework/security/samples/contacts/utils/DataSourcePopulator.java new file mode 100644 index 0000000..af5952b --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/utils/DataSourcePopulator.java @@ -0,0 +1,315 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.samples.contacts.utils; + +import java.util.Random; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.acls.domain.AclImpl; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.MutableAcl; +import org.springframework.security.acls.model.MutableAclService; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.Assert; + + +/** + * Populates the Contacts in-memory database with contact and ACL information. + * + * @author Ben Alex + */ +public class DataSourcePopulator implements InitializingBean { + //~ Instance fields ================================================================================================ + + JdbcTemplate template; + private MutableAclService mutableAclService; + final Random rnd = new Random(); + TransactionTemplate tt; + final String[] firstNames = { + "Bob", "Mary", "James", "Jane", "Kristy", "Kirsty", "Kate", "Jeni", "Angela", "Melanie", "Kent", "William", + "Geoff", "Jeff", "Adrian", "Amanda", "Lisa", "Elizabeth", "Prue", "Richard", "Darin", "Phillip", "Michael", + "Belinda", "Samantha", "Brian", "Greg", "Matthew" + }; + final String[] lastNames = { + "Smith", "Williams", "Jackson", "Rictor", "Nelson", "Fitzgerald", "McAlpine", "Sutherland", "Abbott", "Hall", + "Edwards", "Gates", "Black", "Brown", "Gray", "Marwell", "Booch", "Johnson", "McTaggart", "Parklin", + "Findlay", "Robinson", "Giugni", "Lang", "Chi", "Carmichael" + }; + private int createEntities = 50; + + //~ Methods ======================================================================================================== + public void afterPropertiesSet() throws Exception { + Assert.notNull(template, "dataSource required"); + // Set a user account that will initially own all the created data + Authentication authRequest = new UsernamePasswordAuthenticationToken("rod", "koala", + AuthorityUtils.createAuthorityList("ROLE_IGNORED")); + SecurityContextHolder.getContext().setAuthentication(authRequest); + + template.execute("INSERT INTO USERS(ID,USERNAME,PASSWORD,ENABLED) VALUES(1,'rod','$2a$10$75pBjapg4Nl8Pzd.3JRnUe7PDJmk9qBGwNEJDAlA3V.dEJxcDKn5O',1)"); + template.execute("INSERT INTO USERS(ID,USERNAME,PASSWORD,ENABLED) VALUES(2,'dianne','$2a$04$bCMEyxrdF/7sgfUiUJ6Ose2vh9DAMaVBldS1Bw2fhi1jgutZrr9zm',1)"); + template.execute("INSERT INTO USERS(ID,USERNAME,PASSWORD,ENABLED) VALUES(3,'scott','$2a$06$eChwvzAu3TSexnC3ynw4LOSw1qiEbtNItNeYv5uI40w1i3paoSfLu',1)"); + template.execute("INSERT INTO USERS(ID,USERNAME,PASSWORD,ENABLED) VALUES(4,'peter','$2a$04$8.H8bCMROLF4CIgd7IpeQ.tcBXLP5w8iplO0n.kCIkISwrIgX28Ii',0)"); + template.execute("INSERT INTO USERS(ID,USERNAME,PASSWORD,ENABLED) VALUES(5,'bill','$2a$04$8.H8bCMROLF4CIgd7IpeQ.3khQlPVNWbp8kzSQqidQHGFurim7P8O',1)"); + template.execute("INSERT INTO USERS(ID,USERNAME,PASSWORD,ENABLED) VALUES(6,'bob','$2a$06$zMgxlMf01SfYNcdx7n4NpeFlAGU8apCETz/i2C7VlYWu6IcNyn4Ay',1)"); + template.execute("INSERT INTO USERS(ID,USERNAME,PASSWORD,ENABLED) VALUES(7,'jane','$2a$05$ZrdS7yMhCZ1J.AAidXZhCOxdjD8LO/dhlv4FJzkXA6xh9gdEbBT/u',1)"); + template.execute("INSERT INTO ROLE(ID,NAME) VALUES(1,'ROLE_USER')"); + template.execute("INSERT INTO ROLE(ID,NAME) VALUES(2,'ROLE_SUPERVISOR')"); + template.execute("INSERT INTO USER_ROLES(USER_ID,ROLE_ID) VALUES(1,1)"); + template.execute("INSERT INTO USER_ROLES(USER_ID,ROLE_ID) VALUES(1,2)"); + +// template.execute("INSERT INTO contacts VALUES (1, 'John Smith', 'john@somewhere.com');"); +// template.execute("INSERT INTO contacts VALUES (2, 'Michael Citizen', 'michael@xyz.com');"); +// template.execute("INSERT INTO contacts VALUES (3, 'Joe Bloggs', 'joe@demo.com');"); +// template.execute("INSERT INTO contacts VALUES (4, 'Karen Sutherland', 'karen@sutherland.com');"); +// template.execute("INSERT INTO contacts VALUES (5, 'Mitchell Howard', 'mitchell@abcdef.com');"); +// template.execute("INSERT INTO contacts VALUES (6, 'Rose Costas', 'rose@xyz.com');"); +// template.execute("INSERT INTO contacts VALUES (7, 'Amanda Smith', 'amanda@abcdef.com');"); +// template.execute("INSERT INTO contacts VALUES (8, 'Cindy Smith', 'cindy@smith.com');"); +// template.execute("INSERT INTO contacts VALUES (9, 'Jonathan Citizen', 'jonathan@xyz.com');"); + SecurityContextHolder.clearContext(); + } + public void afterPropertiesSet2() throws Exception { + Assert.notNull(mutableAclService, "mutableAclService required"); + Assert.notNull(template, "dataSource required"); + Assert.notNull(tt, "platformTransactionManager required"); + + // Set a user account that will initially own all the created data + Authentication authRequest = new UsernamePasswordAuthenticationToken("rod", "koala", + AuthorityUtils.createAuthorityList("ROLE_IGNORED")); + SecurityContextHolder.getContext().setAuthentication(authRequest); + + try { + template.execute("DROP TABLE CONTACTS"); + template.execute("DROP TABLE AUTHORITIES"); + template.execute("DROP TABLE USERS"); + template.execute("DROP TABLE ACL_ENTRY"); + template.execute("DROP TABLE ACL_OBJECT_IDENTITY"); + template.execute("DROP TABLE ACL_CLASS"); + template.execute("DROP TABLE ACL_SID"); + } catch(Exception e) { + System.out.println("Failed to drop tables: " + e.getMessage()); + } +/** + template.execute( + "CREATE TABLE ACL_SID(" + + "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY," + + "PRINCIPAL BOOLEAN NOT NULL," + + "SID VARCHAR_IGNORECASE(100) NOT NULL," + + "CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));"); + template.execute( + "CREATE TABLE ACL_CLASS(" + + "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY," + + "CLASS VARCHAR_IGNORECASE(100) NOT NULL," + + "CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));"); + template.execute( + "CREATE TABLE ACL_OBJECT_IDENTITY(" + + "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY," + + "OBJECT_ID_CLASS BIGINT NOT NULL," + + "OBJECT_ID_IDENTITY BIGINT NOT NULL," + + "PARENT_OBJECT BIGINT," + + "OWNER_SID BIGINT," + + "ENTRIES_INHERITING BOOLEAN NOT NULL," + + "CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY)," + + "CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID)," + + "CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID)," + + "CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));"); + template.execute( + "CREATE TABLE ACL_ENTRY(" + + "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY," + + "ACL_OBJECT_IDENTITY BIGINT NOT NULL,ACE_ORDER INT NOT NULL,SID BIGINT NOT NULL," + + "MASK INTEGER NOT NULL,GRANTING BOOLEAN NOT NULL,AUDIT_SUCCESS BOOLEAN NOT NULL," + + "AUDIT_FAILURE BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER)," + + "CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID)," + + "CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));"); + + template.execute( + "CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(500) 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);"); + + template.execute( + "CREATE TABLE CONTACTS(ID BIGINT NOT NULL PRIMARY KEY, CONTACT_NAME VARCHAR_IGNORECASE(50) NOT NULL, EMAIL VARCHAR_IGNORECASE(50) NOT NULL)"); +*/ + /* + Passwords encoded using MD5, NOT in Base64 format, with null as salt + Encoded password for rod is "koala" + Encoded password for dianne is "emu" + Encoded password for scott is "wombat" + Encoded password for peter is "opal" (but user is disabled) + Encoded password for bill is "wombat" + Encoded password for bob is "wombat" + Encoded password for jane is "wombat" + + */ + template.execute("INSERT INTO USERS VALUES('rod','$2a$10$75pBjapg4Nl8Pzd.3JRnUe7PDJmk9qBGwNEJDAlA3V.dEJxcDKn5O',TRUE);"); + template.execute("INSERT INTO USERS VALUES('dianne','$2a$04$bCMEyxrdF/7sgfUiUJ6Ose2vh9DAMaVBldS1Bw2fhi1jgutZrr9zm',TRUE);"); + template.execute("INSERT INTO USERS VALUES('scott','$2a$06$eChwvzAu3TSexnC3ynw4LOSw1qiEbtNItNeYv5uI40w1i3paoSfLu',TRUE);"); + template.execute("INSERT INTO USERS VALUES('peter','$2a$04$8.H8bCMROLF4CIgd7IpeQ.tcBXLP5w8iplO0n.kCIkISwrIgX28Ii',FALSE);"); + template.execute("INSERT INTO USERS VALUES('bill','$2a$04$8.H8bCMROLF4CIgd7IpeQ.3khQlPVNWbp8kzSQqidQHGFurim7P8O',TRUE);"); + template.execute("INSERT INTO USERS VALUES('bob','$2a$06$zMgxlMf01SfYNcdx7n4NpeFlAGU8apCETz/i2C7VlYWu6IcNyn4Ay',TRUE);"); + template.execute("INSERT INTO USERS VALUES('jane','$2a$05$ZrdS7yMhCZ1J.AAidXZhCOxdjD8LO/dhlv4FJzkXA6xh9gdEbBT/u',TRUE);"); + template.execute("INSERT INTO AUTHORITIES VALUES('rod','ROLE_USER');"); + template.execute("INSERT INTO AUTHORITIES VALUES('rod','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');"); + template.execute("INSERT INTO AUTHORITIES VALUES('bill','ROLE_USER');"); + template.execute("INSERT INTO AUTHORITIES VALUES('bob','ROLE_USER');"); + template.execute("INSERT INTO AUTHORITIES VALUES('jane','ROLE_USER');"); + + template.execute("INSERT INTO contacts VALUES (1, 'John Smith', 'john@somewhere.com');"); + template.execute("INSERT INTO contacts VALUES (2, 'Michael Citizen', 'michael@xyz.com');"); + template.execute("INSERT INTO contacts VALUES (3, 'Joe Bloggs', 'joe@demo.com');"); + template.execute("INSERT INTO contacts VALUES (4, 'Karen Sutherland', 'karen@sutherland.com');"); + template.execute("INSERT INTO contacts VALUES (5, 'Mitchell Howard', 'mitchell@abcdef.com');"); + template.execute("INSERT INTO contacts VALUES (6, 'Rose Costas', 'rose@xyz.com');"); + template.execute("INSERT INTO contacts VALUES (7, 'Amanda Smith', 'amanda@abcdef.com');"); + template.execute("INSERT INTO contacts VALUES (8, 'Cindy Smith', 'cindy@smith.com');"); + template.execute("INSERT INTO contacts VALUES (9, 'Jonathan Citizen', 'jonathan@xyz.com');"); + + for (int i = 10; i < createEntities; i++) { + String[] person = selectPerson(); + template.execute("INSERT INTO contacts VALUES (" + i + ", '" + person[2] + "', '" + person[0].toLowerCase() + + "@" + person[1].toLowerCase() + ".com');"); + } + + // Create acl_object_identity rows (and also acl_class rows as needed + for (int i = 1; i < createEntities; i++) { + final ObjectIdentity objectIdentity = new ObjectIdentityImpl(Contact.class, new Long(i)); + tt.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus arg0) { + mutableAclService.createAcl(objectIdentity); + + return null; + } + }); + } + + // Now grant some permissions + grantPermissions(1, "rod", BasePermission.ADMINISTRATION); + grantPermissions(2, "rod", BasePermission.READ); + grantPermissions(3, "rod", BasePermission.READ); + grantPermissions(3, "rod", BasePermission.WRITE); + grantPermissions(3, "rod", BasePermission.DELETE); + grantPermissions(4, "rod", BasePermission.ADMINISTRATION); + grantPermissions(4, "dianne", BasePermission.ADMINISTRATION); + grantPermissions(4, "scott", BasePermission.READ); + grantPermissions(5, "dianne", BasePermission.ADMINISTRATION); + grantPermissions(5, "dianne", BasePermission.READ); + grantPermissions(6, "dianne", BasePermission.READ); + grantPermissions(6, "dianne", BasePermission.WRITE); + grantPermissions(6, "dianne", BasePermission.DELETE); + grantPermissions(6, "scott", BasePermission.READ); + grantPermissions(7, "scott", BasePermission.ADMINISTRATION); + grantPermissions(8, "dianne", BasePermission.ADMINISTRATION); + grantPermissions(8, "dianne", BasePermission.READ); + grantPermissions(8, "scott", BasePermission.READ); + grantPermissions(9, "scott", BasePermission.ADMINISTRATION); + grantPermissions(9, "scott", BasePermission.READ); + grantPermissions(9, "scott", BasePermission.WRITE); + grantPermissions(9, "scott", BasePermission.DELETE); + + // Now expressly change the owner of the first ten contacts + // We have to do this last, because "rod" owns all of them (doing it sooner would prevent ACL updates) + // Note that ownership has no impact on permissions - they're separate (ownership only allows ACl editing) + changeOwner(5, "dianne"); + changeOwner(6, "dianne"); + changeOwner(7, "scott"); + changeOwner(8, "dianne"); + changeOwner(9, "scott"); + + String[] users = {"bill", "bob", "jane"}; // don't want to mess around with consistent sample data + Permission[] permissions = {BasePermission.ADMINISTRATION, BasePermission.READ, BasePermission.DELETE}; + + for (int i = 10; i < createEntities; i++) { + String user = users[rnd.nextInt(users.length)]; + Permission permission = permissions[rnd.nextInt(permissions.length)]; + grantPermissions(i, user, permission); + + String user2 = users[rnd.nextInt(users.length)]; + Permission permission2 = permissions[rnd.nextInt(permissions.length)]; + grantPermissions(i, user2, permission2); + } + + SecurityContextHolder.clearContext(); + } + + private void changeOwner(int contactNumber, String newOwnerUsername) { + AclImpl acl = (AclImpl) mutableAclService.readAclById(new ObjectIdentityImpl(Contact.class, + new Long(contactNumber))); + acl.setOwner(new PrincipalSid(newOwnerUsername)); + updateAclInTransaction(acl); + } + + public int getCreateEntities() { + return createEntities; + } + + private void grantPermissions(int contactNumber, String recipientUsername, Permission permission) { + AclImpl acl = (AclImpl) mutableAclService.readAclById(new ObjectIdentityImpl(Contact.class, + new Long(contactNumber))); + acl.insertAce(acl.getEntries().size(), permission, new PrincipalSid(recipientUsername), true); + updateAclInTransaction(acl); + } + + private String[] selectPerson() { + String firstName = firstNames[rnd.nextInt(firstNames.length)]; + String lastName = lastNames[rnd.nextInt(lastNames.length)]; + + return new String[] {firstName, lastName, firstName + " " + lastName}; + } + + public void setCreateEntities(int createEntities) { + this.createEntities = createEntities; + } + + public void setDataSource(DataSource dataSource) { + this.template = new JdbcTemplate(dataSource); + } + + public void setMutableAclService(MutableAclService mutableAclService) { + this.mutableAclService = mutableAclService; + } + + public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) { + this.tt = new TransactionTemplate(platformTransactionManager); + } + + private void updateAclInTransaction(final MutableAcl acl) { + tt.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus arg0) { + mutableAclService.updateAcl(acl); + + return null; + } + }); + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/web/AddDeleteContactController.java b/src/main/java/org/springframework/security/samples/contacts/web/AddDeleteContactController.java new file mode 100644 index 0000000..083865f --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/web/AddDeleteContactController.java @@ -0,0 +1,69 @@ +package org.springframework.security.samples.contacts.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.security.samples.contacts.service.ContactManager; +import org.springframework.security.samples.contacts.web.model.WebContact; +import org.springframework.security.samples.contacts.web.validator.WebContactValidator; +import org.springframework.stereotype.Controller; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Validator; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; + +/** + * + * @author Luke Taylor + * @since 3.0 + */ +@Controller +public class AddDeleteContactController { + @Autowired + private ContactManager contactManager; + private final Validator validator = new WebContactValidator(); + + /** + * Displays the "add contact" form. + */ + @RequestMapping(value="/secure/add.htm", method=RequestMethod.GET) + public ModelAndView addContactDisplay() { + return new ModelAndView("add", "webContact", new WebContact()); + } + + @InitBinder + public void initBinder(WebDataBinder binder) { + System.out.println("A binder for object: " + binder.getObjectName()); + } + + /** + * Handles the submission of the contact form, creating a new instance if + * the username and email are valid. + */ + @RequestMapping(value="/secure/add.htm", method=RequestMethod.POST) + public String addContact(WebContact form, BindingResult result) { + validator.validate(form, result); + + if (result.hasErrors()) { + return "add"; + } + + Contact contact = new Contact(); + contact.setName(form.getName()); + contact.setEmail(form.getEmail()); + contactManager.create(contact); + + return "redirect:/secure/index.htm"; + } + + @RequestMapping(value="/secure/del.htm", method=RequestMethod.GET) + public ModelAndView handleRequest(@RequestParam("contactId") int contactId) { + Contact contact = contactManager.getById(Long.valueOf(contactId)); + contactManager.delete(contact); + + return new ModelAndView("deleted", "contact", contact); + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/web/AdminPermissionController.java b/src/main/java/org/springframework/security/samples/contacts/web/AdminPermissionController.java new file mode 100644 index 0000000..48c41b1 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/web/AdminPermissionController.java @@ -0,0 +1,172 @@ +package org.springframework.security.samples.contacts.web; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.dao.DataAccessException; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.domain.DefaultPermissionFactory; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PermissionFactory; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.Acl; +import org.springframework.security.acls.model.AclService; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.security.samples.contacts.service.ContactManager; +import org.springframework.security.samples.contacts.web.model.AddPermission; +import org.springframework.security.samples.contacts.web.validator.AddPermissionValidator; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Validator; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; +import org.springframework.web.servlet.ModelAndView; + + +/** + * Web controller to handle Permission administration functions - adding and deleting + * permissions for contacts. + * + * @author Luke Taylor + * @since 3.0 + */ +@Controller +@SessionAttributes("addPermission") +public final class AdminPermissionController implements MessageSourceAware{ + @Autowired + private AclService aclService; + @Autowired + private ContactManager contactManager; + private MessageSourceAccessor messages; + private final Validator addPermissionValidator = new AddPermissionValidator(); + private final PermissionFactory permissionFactory = new DefaultPermissionFactory(); + + /** + * Displays the permission admin page for a particular contact. + */ + @RequestMapping(value="/secure/adminPermission.htm", method=RequestMethod.GET) + public ModelAndView displayAdminPage(@RequestParam("contactId") int contactId) { + Contact contact = contactManager.getById(Long.valueOf(contactId)); + Acl acl = aclService.readAclById(new ObjectIdentityImpl(contact)); + + Map model = new HashMap(); + model.put("contact", contact); + model.put("acl", acl); + + return new ModelAndView("adminPermission", "model", model); + } + + /** + * Displays the "add permission" page for a contact. + */ + @RequestMapping(value="/secure/addPermission.htm", method=RequestMethod.GET) + public ModelAndView displayAddPermissionPageForContact(@RequestParam("contactId") int contactId) { + Contact contact = contactManager.getById(new Long(contactId)); + + AddPermission addPermission = new AddPermission(); + addPermission.setContact(contact); + + Map model = new HashMap(); + model.put("addPermission", addPermission); + model.put("recipients", listRecipients()); + model.put("permissions", listPermissions()); + + return new ModelAndView("addPermission", model); + } + + @InitBinder("addPermission") + public void initBinder(WebDataBinder binder) { + binder.setAllowedFields("recipient", "permission"); + } + + /** + * Handles submission of the "add permission" form. + */ + @RequestMapping(value="/secure/addPermission.htm", method=RequestMethod.POST) + public String addPermission(AddPermission addPermission, BindingResult result, ModelMap model) { + addPermissionValidator.validate(addPermission, result); + + if (result.hasErrors()) { + model.put("recipients", listRecipients()); + model.put("permissions", listPermissions()); + + return "addPermission"; + } + + PrincipalSid sid = new PrincipalSid(addPermission.getRecipient()); + Permission permission = permissionFactory.buildFromMask(addPermission.getPermission()); + + try { + contactManager.addPermission(addPermission.getContact(), sid, permission); + } catch (DataAccessException existingPermission) { + existingPermission.printStackTrace(); + result.rejectValue("recipient", "err.recipientExistsForContact", "Addition failure."); + + model.put("recipients", listRecipients()); + model.put("permissions", listPermissions()); + return "addPermission"; + } + + return "redirect:/secure/index.htm"; + } + + /** + * Deletes a permission + */ + @RequestMapping(value="/secure/deletePermission.htm") + public ModelAndView deletePermission( + @RequestParam("contactId") int contactId, + @RequestParam("sid") String sid, + @RequestParam("permission") int mask) { + + Contact contact = contactManager.getById(new Long(contactId)); + + Sid sidObject = new PrincipalSid(sid); + Permission permission = permissionFactory.buildFromMask(mask); + + contactManager.deletePermission(contact, sidObject, permission); + + Map model = new HashMap(); + model.put("contact", contact); + model.put("sid", sidObject); + model.put("permission", permission); + + return new ModelAndView("deletePermission", "model", model); + } + + private Map listPermissions() { + Map map = new LinkedHashMap(); + map.put(Integer.valueOf(BasePermission.ADMINISTRATION.getMask()), messages.getMessage("select.administer", "Administer")); + map.put(Integer.valueOf(BasePermission.READ.getMask()), messages.getMessage("select.read", "Read")); + map.put(Integer.valueOf(BasePermission.DELETE.getMask()), messages.getMessage("select.delete", "Delete")); + + return map; + } + + private Map listRecipients() { + Map map = new LinkedHashMap(); + map.put("", messages.getMessage("select.pleaseSelect", "-- please select --")); + + for (String recipient : contactManager.getAllRecipients()) { + map.put(recipient, recipient); + } + + return map; + } + + public void setMessageSource(MessageSource messageSource) { + this.messages = new MessageSourceAccessor(messageSource); + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/web/IndexController.java b/src/main/java/org/springframework/security/samples/contacts/web/IndexController.java new file mode 100644 index 0000000..2a125bd --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/web/IndexController.java @@ -0,0 +1,84 @@ +package org.springframework.security.samples.contacts.web; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.acls.AclPermissionEvaluator; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.samples.contacts.entity.Contact; +import org.springframework.security.samples.contacts.service.ContactManager; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +/** + * Controller which handles simple, single request use cases such as index pages and contact deletion. + * + * @author Luke Taylor + * @since 3.0 + */ +@Controller +public class IndexController { + private final static Permission[] HAS_DELETE = new Permission[] {BasePermission.DELETE, BasePermission.ADMINISTRATION}; + private final static Permission[] HAS_ADMIN = new Permission[] {BasePermission.ADMINISTRATION}; + + //~ Instance fields ================================================================================================ + + @Autowired + private ContactManager contactManager; + @Autowired + private PermissionEvaluator permissionEvaluator; + + //~ Methods ======================================================================================================== + + /** + * The public index page, used for unauthenticated users. + */ + @RequestMapping(value="/hello.htm", method=RequestMethod.GET) + public ModelAndView displayPublicIndex() { + Contact rnd = contactManager.getRandomContact(); + + return new ModelAndView("hello", "contact", rnd); + } + + /** + * The index page for an authenticated user. + *

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

+ * 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. + */ + @RequestMapping(value="/secure/index.htm", method=RequestMethod.GET) + public ModelAndView displayUserContacts() { + List myContactsList = contactManager.getAll(); + Map hasDelete = new HashMap(myContactsList.size()); + Map hasAdmin = new HashMap(myContactsList.size()); + + Authentication user = SecurityContextHolder.getContext().getAuthentication(); + + for (Contact contact : myContactsList) { + hasDelete.put(contact, Boolean.valueOf(permissionEvaluator.hasPermission(user, contact, HAS_DELETE))); + hasAdmin.put(contact, Boolean.valueOf(permissionEvaluator.hasPermission(user, contact, HAS_ADMIN))); + } + + Map model = new HashMap(); + model.put("contacts", myContactsList); + model.put("hasDeletePermission", hasDelete); + model.put("hasAdminPermission", hasAdmin); + + return new ModelAndView("index", "model", model); + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/web/model/AddPermission.java b/src/main/java/org/springframework/security/samples/contacts/web/model/AddPermission.java new file mode 100644 index 0000000..6873195 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/web/model/AddPermission.java @@ -0,0 +1,58 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.samples.contacts.web.model; + +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.samples.contacts.entity.Contact; + + +/** + * Model object for add permission use case. + * + * @author Ben Alex + */ +public class AddPermission { + //~ Instance fields ================================================================================================ + + public Contact contact; + public Integer permission = BasePermission.READ.getMask(); + public String recipient; + + //~ Methods ======================================================================================================== + + public Contact getContact() { + return contact; + } + + public Integer getPermission() { + return permission; + } + + public String getRecipient() { + return recipient; + } + + public void setContact(Contact contact) { + this.contact = contact; + } + + public void setPermission(Integer permission) { + this.permission = permission; + } + + public void setRecipient(String recipient) { + this.recipient = recipient; + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/web/model/WebContact.java b/src/main/java/org/springframework/security/samples/contacts/web/model/WebContact.java new file mode 100644 index 0000000..ca5f383 --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/web/model/WebContact.java @@ -0,0 +1,46 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.samples.contacts.web.model; + +/** + * An object that represents user-editable sections of a {@link Contact}. + * + * @author Ben Alex + */ +public class WebContact { + //~ Instance fields ================================================================================================ + + private String email; + private String name; + + //~ Methods ======================================================================================================== + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/org/springframework/security/samples/contacts/web/validator/AddPermissionValidator.java b/src/main/java/org/springframework/security/samples/contacts/web/validator/AddPermissionValidator.java new file mode 100644 index 0000000..99d244b --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/web/validator/AddPermissionValidator.java @@ -0,0 +1,59 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.samples.contacts.web.validator; + +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.samples.contacts.web.model.AddPermission; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + + +/** + * Validates {@link AddPermission}. + * + * @author Ben Alex + */ +public class AddPermissionValidator implements Validator { + //~ Methods ======================================================================================================== + + @SuppressWarnings("unchecked") + 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 != BasePermission.ADMINISTRATION.getMask()) + && (permission != BasePermission.READ.getMask()) && (permission != BasePermission.DELETE.getMask())) { + 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/src/main/java/org/springframework/security/samples/contacts/web/validator/WebContactValidator.java b/src/main/java/org/springframework/security/samples/contacts/web/validator/WebContactValidator.java new file mode 100644 index 0000000..ba21e1f --- /dev/null +++ b/src/main/java/org/springframework/security/samples/contacts/web/validator/WebContactValidator.java @@ -0,0 +1,47 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.samples.contacts.web.validator; + +import org.springframework.security.samples.contacts.web.model.WebContact; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + + +/** + * Validates {@link WebContact}. + * + * @author Ben Alex + */ +public class WebContactValidator implements Validator { + //~ Methods ======================================================================================================== + + @SuppressWarnings("unchecked") + public boolean supports(Class clazz) { + return clazz.equals(WebContact.class); + } + + public void validate(Object obj, Errors errors) { + WebContact wc = (WebContact) obj; + + 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) || (wc.getEmail().length() > 50)) { + errors.rejectValue("email", "err.email", "Email 3-50 characters is required. *"); + } + } +} diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..97c02f9 --- /dev/null +++ b/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db.properties b/src/main/resources/db.properties new file mode 100644 index 0000000..67dfd6b --- /dev/null +++ b/src/main/resources/db.properties @@ -0,0 +1,23 @@ +#db.username=sa +#db.password= +#db.driver=org.hsqldb.jdbcDriver +#db.url=jdbc:hsqldb:mem:app-db +#db.dialect=org.hibernate.dialect.HSQLDialect + + +#MySQL Options +#db.password=password +#db.username=username +#db.url=jdbc:mysql://localhost/dbname +#db.dialect=org.hibernate.dialect.MySQL5Dialect +#db.driver=com.mysql.jdbc.Driver + + +db.username=APP +db.password=anyting +db.driver=org.apache.derby.jdbc.ClientDriver +db.url=jdbc:derby://localhost:1527/sample +db.dialect=org.hibernate.dialect.DerbyDialect + + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..aa5aa97 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties new file mode 100644 index 0000000..058905c --- /dev/null +++ b/src/main/resources/messages.properties @@ -0,0 +1,6 @@ +err.name=Name 3-50 characters is required. +err.email=Email 3-50 characters is required. +err.permission=Permission is required. +err.recipient=Recipient is required. +err.permission.invalid=The indicated permission is invalid. +err.recipient.length=The recipient is too long (maximum 100 characters). \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/contacts-servlet.xml b/src/main/webapp/WEB-INF/contacts-servlet.xml new file mode 100644 index 0000000..31f6007 --- /dev/null +++ b/src/main/webapp/WEB-INF/contacts-servlet.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + /WEB-INF/tiles/tiles-common.xml + /WEB-INF/tiles/tiles-user.xml + + + + + + + + org.springframework.web.servlet.view.tiles2.TilesView + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/jsp/add.jsp b/src/main/webapp/WEB-INF/jsp/add.jsp new file mode 100644 index 0000000..02630f9 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/add.jsp @@ -0,0 +1,42 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + +Add New Contact + +

Add Contact

+
+ + + + + + + + + + + + + + + +
Name: + "> + + +
Email: + "> + + +
+
+ + Please fix all errors! + +

+ + " value=""/> + + +">Home + + diff --git a/src/main/webapp/WEB-INF/jsp/addPermission.jsp b/src/main/webapp/WEB-INF/jsp/addPermission.jsp new file mode 100644 index 0000000..dfd2476 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/addPermission.jsp @@ -0,0 +1,56 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + +Add Permission + +

Add Permission

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

+ " value=""/> + + +

+">Admin Permission ">Manage + + diff --git a/src/main/webapp/WEB-INF/jsp/adminPermission.jsp b/src/main/webapp/WEB-INF/jsp/adminPermission.jsp new file mode 100644 index 0000000..2e0d43f --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/adminPermission.jsp @@ -0,0 +1,30 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + + +Administer Permissions + +

Administer Permissions

+

+ + + +

+ + + + + + + +
+ + + + + ">Del +
+

+">Add Permission ">Manage +

+ + diff --git a/src/main/webapp/WEB-INF/jsp/deletePermission.jsp b/src/main/webapp/WEB-INF/jsp/deletePermission.jsp new file mode 100644 index 0000000..85a71a2 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/deletePermission.jsp @@ -0,0 +1,20 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + + +Permission Deleted + +

Permission Deleted

+

+ + + +

+ + + + + + +

">Manage + + diff --git a/src/main/webapp/WEB-INF/jsp/deleted.jsp b/src/main/webapp/WEB-INF/jsp/deleted.jsp new file mode 100644 index 0000000..8fed87c --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/deleted.jsp @@ -0,0 +1,13 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + + +Deletion completed + +

Deleted

+

+ + + +

">Manage + + diff --git a/src/main/webapp/WEB-INF/jsp/frames.jsp b/src/main/webapp/WEB-INF/jsp/frames.jsp new file mode 100644 index 0000000..96e9289 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/frames.jsp @@ -0,0 +1,10 @@ + + +Frames + + +

This contains frames, but the frames will not be loaded due to the X-Frame-Options +being specified as denied. This protects against clickjacking attacks

+ + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/hello.jsp b/src/main/webapp/WEB-INF/jsp/hello.jsp new file mode 100644 index 0000000..8713e0a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/hello.jsp @@ -0,0 +1,52 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + + +Contacts Security Demo + +

Contacts Security Demo

+

Contacts demonstrates the following central Spring Security capabilities: +

    +
  • Role-based security. Each principal is a member of certain roles, + which are used to restrict access to certain secure objects.
  • +
  • Domain object instance security. The 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.
  • +
  • Method invocation security. The ContactManager service + layer bean has a number of secured (protected) and public (unprotected) + methods.
  • +
  • Web request security. The /secure URI path is protected + by Spring Security from principals not holding the + ROLE_USER granted authority.
  • +
  • Security unaware application objects. None of the objects + are aware of the security being implemented by Spring Security. *
  • +
  • Security taglib usage. All of the JSPs use Spring Security's + taglib to evaluate security information. *
  • +
  • Fully declarative security. Every capability is configured in + the application context using standard Spring Security classes. *
  • +
  • Database-sourced security data. All of the user, role and ACL + information is obtained from an in-memory JDBC-compliant database.
  • +
  • Integrated form-based and BASIC authentication. Any BASIC + authentication header is detected and used for authentication. Normal + interactive form-based authentication is used by default.
  • +
  • Remember-me services. Spring Security's pluggable remember-me + strategy is demonstrated, with a corresponding checkbox on the login form.
  • +
+ +* As the application provides an "ACL Administration" use case, those +classes are necessarily aware of security. But no business use cases are. + +

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 +">Frames + + diff --git a/src/main/webapp/WEB-INF/jsp/include.jsp b/src/main/webapp/WEB-INF/jsp/include.jsp new file mode 100644 index 0000000..3b31878 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/include.jsp @@ -0,0 +1,6 @@ +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ page pageEncoding="UTF-8" %> diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp new file mode 100644 index 0000000..1945221 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -0,0 +1,32 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + + +Your Contacts + +

's Contacts

+

+ + + + + + + + + + + + + + + +
idNameEmail
+ + + + + + ">Del">Admin Permission
+

">Add

">Logoff (also clears any remember-me cookie) + + diff --git a/src/main/webapp/WEB-INF/jsp/layout/footer.jsp b/src/main/webapp/WEB-INF/jsp/layout/footer.jsp new file mode 100644 index 0000000..56e407b --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/layout/footer.jsp @@ -0,0 +1 @@ +Footer \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/layout/header.jsp b/src/main/webapp/WEB-INF/jsp/layout/header.jsp new file mode 100644 index 0000000..78e5a6a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/layout/header.jsp @@ -0,0 +1 @@ +

Header

diff --git a/src/main/webapp/WEB-INF/jsp/layout/hello.jsp b/src/main/webapp/WEB-INF/jsp/layout/hello.jsp new file mode 100644 index 0000000..8c2a09b --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/layout/hello.jsp @@ -0,0 +1,8 @@ + + + Spring 3.0 MVC Series: Hello World - ViralPatel.net + + + ${message} + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/layout/layout.jsp b/src/main/webapp/WEB-INF/jsp/layout/layout.jsp new file mode 100644 index 0000000..5b4d1d7 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/layout/layout.jsp @@ -0,0 +1,25 @@ +<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%> + + + + +<tiles:insertAttribute name="title" ignore="true" /> + + + + + + + + + + + + + +
+
+
+ + diff --git a/src/main/webapp/WEB-INF/jsp/layout/menu.jsp b/src/main/webapp/WEB-INF/jsp/layout/menu.jsp new file mode 100644 index 0000000..9d70cd7 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/layout/menu.jsp @@ -0,0 +1 @@ +Menu \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/login.jsp b/src/main/webapp/WEB-INF/jsp/login.jsp new file mode 100644 index 0000000..1b3fb58 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/login.jsp @@ -0,0 +1,47 @@ +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ page pageEncoding="UTF-8" %> + + + + Login + + + +

Login

+ +

Valid users: +

+

username rod, password koala +

username dianne, password emu +

username scott, password wombat +

username peter, password opal (user disabled) +

username bill, password wombat +

username bob, password wombat +

username jane, password wombat +

+ +

Locale is: <%= request.getLocale() %>

+ <%-- this form-login-page form is also used as the + form-error-page to ask for a login again. + --%> + + + Your login attempt was not successful, try again.

+ Reason: . +
+
+ +
+ + + + + + + +
User:
Password:
Don't ask for my password for two weeks
+ " value=""/> + + + + diff --git a/src/main/webapp/WEB-INF/spring/applicationContext-common-authorization.xml b/src/main/webapp/WEB-INF/spring/applicationContext-common-authorization.xml new file mode 100644 index 0000000..e46322b --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/applicationContext-common-authorization.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/spring/applicationContext-common-business.xml b/src/main/webapp/WEB-INF/spring/applicationContext-common-business.xml new file mode 100644 index 0000000..483871f --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/applicationContext-common-business.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/spring/applicationContext-security.xml b/src/main/webapp/WEB-INF/spring/applicationContext-security.xml new file mode 100644 index 0000000..e9c067d --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/applicationContext-security.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/spring/db.xml b/src/main/webapp/WEB-INF/spring/db.xml new file mode 100644 index 0000000..f32aabf --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/db.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/spring/remoting-servlet.xmll b/src/main/webapp/WEB-INF/spring/remoting-servlet.xmll new file mode 100644 index 0000000..0044b60 --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/remoting-servlet.xmll @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/spring/root-context.xml b/src/main/webapp/WEB-INF/spring/root-context.xml new file mode 100644 index 0000000..eaf80d3 --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/root-context.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/tiles/tiles-common.xml b/src/main/webapp/WEB-INF/tiles/tiles-common.xml new file mode 100644 index 0000000..b90acdc --- /dev/null +++ b/src/main/webapp/WEB-INF/tiles/tiles-common.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/tiles/tiles-user.xml b/src/main/webapp/WEB-INF/tiles/tiles-user.xml new file mode 100644 index 0000000..a8d3625 --- /dev/null +++ b/src/main/webapp/WEB-INF/tiles/tiles-user.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..bf6862a --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,96 @@ + + + + + + Contacts Sample Application + + + + contextConfigLocation + /WEB-INF/spring/root-context.xml + + + + + + webAppRootKey + contacts.root + + + + localizationFilter + org.springframework.web.filter.RequestContextFilter + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + + localizationFilter + /* + + + + springSecurityFilterChain + /* + + + + + org.springframework.web.context.ContextLoaderListener + + + + + contacts + org.springframework.web.servlet.DispatcherServlet + 1 + + + + + + contacts + *.htm + + + + index.jsp + + + + 403 + /error.html + + + diff --git a/src/main/webapp/accessDenied.jsp b/src/main/webapp/accessDenied.jsp new file mode 100644 index 0000000..c94ae99 --- /dev/null +++ b/src/main/webapp/accessDenied.jsp @@ -0,0 +1,22 @@ +<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %> +<%@ page import="org.springframework.security.core.Authentication" %> + + + + Access Denied + + + +

Sorry, access is denied

+ +

+<%= request.getAttribute("SPRING_SECURITY_403_EXCEPTION")%> +

+

+<% Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { %> + Authentication object as a String: <%= auth.toString() %>

+<% } %> +

+ + diff --git a/src/main/webapp/error.html b/src/main/webapp/error.html new file mode 100644 index 0000000..3c58108 --- /dev/null +++ b/src/main/webapp/error.html @@ -0,0 +1,5 @@ + + Access denied! +

Access Denied

+

We're sorry, but you are not authorized to perform the requested operation.

+ \ No newline at end of file diff --git a/src/main/webapp/exitUser.jsp b/src/main/webapp/exitUser.jsp new file mode 100644 index 0000000..b0792e5 --- /dev/null +++ b/src/main/webapp/exitUser.jsp @@ -0,0 +1,39 @@ +<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core' %> + +<%@ page import="org.springframework.security.core.Authentication" %> +<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %> +<%@ page pageEncoding="UTF-8" %> + + + + Exit User + + + +

Exit User

+ + + + Your 'Exit User' attempt was not successful, try again.

+ Reason: +
+
+ +
+ + + +
Current User: + +<% + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { %> + + <%= auth.getPrincipal().toString() %> + + <% } %> +
+ " value=""/> + + + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 0000000..4c86e33 --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,4 @@ +<%@ include file="/WEB-INF/jsp/include.jsp" %> + +<%-- Redirected because we can't set the welcome page to a virtual URL. --%> + diff --git a/src/main/webapp/login.jsp b/src/main/webapp/login.jsp new file mode 100644 index 0000000..1b3fb58 --- /dev/null +++ b/src/main/webapp/login.jsp @@ -0,0 +1,47 @@ +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ page pageEncoding="UTF-8" %> + + + + Login + + + +

Login

+ +

Valid users: +

+

username rod, password koala +

username dianne, password emu +

username scott, password wombat +

username peter, password opal (user disabled) +

username bill, password wombat +

username bob, password wombat +

username jane, password wombat +

+ +

Locale is: <%= request.getLocale() %>

+ <%-- this form-login-page form is also used as the + form-error-page to ask for a login again. + --%> + + + Your login attempt was not successful, try again.

+ Reason: . +
+
+ +
+ + + + + + + +
User:
Password:
Don't ask for my password for two weeks
+ " value=""/> + + + + diff --git a/src/main/webapp/secure/debug.jsp b/src/main/webapp/secure/debug.jsp new file mode 100644 index 0000000..553bc3a --- /dev/null +++ b/src/main/webapp/secure/debug.jsp @@ -0,0 +1,40 @@ +<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %> +<%@ page import="org.springframework.security.core.Authentication" %> +<%@ page import="org.springframework.security.core.GrantedAuthority" %> + + + +Security Debug Information + + + +

Security Debug Information

+ +<% + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { %> +

+ Authentication object is of type: <%= auth.getClass().getName() %> +

+

+ Authentication object as a String:

<%= auth.toString() %> +

+ + Authentication object holds the following granted authorities:

+<% + for (GrantedAuthority authority : auth.getAuthorities()) { %> + <%= authority %> (getAuthority(): <%= authority.getAuthority() %>)
+<% } +%> + +

Success! Your web filters appear to be properly configured!

+<% + } else { +%> + Authentication object is null.
+ This is an error and your Spring Security application will not operate properly until corrected.

+<% } +%> + + + diff --git a/src/main/webapp/switchUser.jsp b/src/main/webapp/switchUser.jsp new file mode 100644 index 0000000..f052fe2 --- /dev/null +++ b/src/main/webapp/switchUser.jsp @@ -0,0 +1,43 @@ +<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %> +<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" %> +<%@ page import="org.springframework.security.core.AuthenticationException" %> +<%@ page pageEncoding="UTF-8" %> + + + + Switch User + + + +

Switch to User

+ +

Valid users:

+ +

username rod, password koala

+

username dianne, password emu

+

username scott, password wombat

+

username bill, password wombat

+

username bob, password wombat

+

username jane, password wombat

+ <%-- this form-login-page form is also used as the + form-error-page to ask for a login again. + --%> + +

+ + Your 'su' attempt was not successful, try again.

+ Reason: <%= ((AuthenticationException) session.getAttribute(AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %> +
+

+
+ +
+ + + +
User:
+ " value=""/> + + + + diff --git a/src/site/resources/sslhowto.txt b/src/site/resources/sslhowto.txt new file mode 100644 index 0000000..add60a2 --- /dev/null +++ b/src/site/resources/sslhowto.txt @@ -0,0 +1,99 @@ +$Id$ + +CAS requires HTTPS be used for all operations, with the certificate used +having been signed by a certificate in the cacerts files shipped with Java. + +If you're using a HTTPS certificate signed by a well known authority +(like Verisign), you can safely ignore the procedure below (although you +might find the troubleshooting section at the end helpful). + +The following demonstrates how to create a self-signed certificate and add +it to the cacerts file. If you just want to use the certificate we have +already created and shipped with Spring Security, you +can skip directly to step 3. + + +1. keytool -keystore keystore -alias acegisecurity -genkey -keyalg RSA -validity 9999 -storepass password -keypass password + +What is your first and last name? + [Unknown]: localhost +What is the name of your organizational unit? + [Unknown]: Spring Security +What is the name of your organization? + [Unknown]: TEST CERTIFICATE ONLY. DO NOT USE IN PRODUCTION. +What is the name of your City or Locality? + [Unknown]: +What is the name of your State or Province? + [Unknown]: +What is the two-letter country code for this unit? + [Unknown]: +Is CN=localhost, OU=Spring Security, O=TEST CERTIFICATE ONLY. D +O NOT USE IN PRODUCTION., L=Unknown, ST=Unknown, C=Unknown correct? + [no]: yes + + +2. keytool -export -v -rfc -alias acegisecurity -file acegisecurity.txt -keystore keystore -storepass password + +3. copy acegisecurity.txt %JAVA_HOME%\lib\security + +4. copy keystore %YOUR_WEB_CONTAINER_LOCATION% + + NOTE: You will need to configure your web container as appropriate. + We recommend you test the certificate works by visiting + https://localhost:8443. When prompted by your browser, select to + install the certificate. + +5. cd %JAVA_HOME%\lib\security + +6. keytool -import -v -file acegisecurity.txt -keypass password -keystore cacerts -storepass changeit -alias acegisecurity + +Owner: CN=localhost, OU=Spring Security, O=TEST CERTIFICATE ONL +Y. DO NOT USE IN PRODUCTION., L=Unknown, ST=Unknown, C=Unknown +Issuer: CN=localhost, OU=Spring Security, O=TEST CERTIFICATE ON +LY. DO NOT USE IN PRODUCTION., L=Unknown, ST=Unknown, C=Unknown +Serial number: 4080daf4 +Valid from: Sat Apr 17 07:21:24 GMT 2004 until: Tue Sep 02 07:21:24 GMT 2031 +Certificate fingerprints: + MD5: B4:AC:A8:24:34:99:F1:A9:F8:1D:A5:6C:BF:0A:34:FA + SHA1: F1:E6:B1:3A:01:39:2D:CF:06:FA:82:AB:86:0D:77:9D:06:93:D6:B0 +Trust this certificate? [no]: yes +Certificate was added to keystore +[Saving cacerts] + + +7. Finished. You can now run the sample application as if you purchased a + properly signed certificate. For production applications, of course you should + use an appropriately signed certificate so your web visitors will trust it + (such as issued by Thawte, Verisign etc). + +TROUBLESHOOTING + +* First of all, most CAS-Acegi Security problems are because of untrusted + SSL certificates. So it's important to understand why. Most people can + load the Acegi Security webapp, get redirected to the CAS server, then + after login they get redirected back to the Acegi Security webapp and + receive a failure. This is because the CAS server redirects to something + like https://server3.company.com/webapp/j_spring_cas_security_check?ticket=ST-0-ER94xMJmn6pha35CQRoZ + which causes the "service ticket" (the "ticket" parameter) to be validated. + net.sf.acegisecurity.providers.cas.ticketvalidator.CasProxyTicketValidator + performs service ticket validation by delegation to CAS' + ProxyTicketValidator class. The ProxyTicketValidator class will perform a + HTTPS connection from the web server running the Acegi Security webapp + (server3.company.com) above to the CAS server. If for some reason the + web server keystore does not trust the HTTPS certificate presented by the + CAS server, you will receive various failures as discussed below. NB: This + has NOTHING to do with client-side (browser) certificates. You need to + correct the trust between the two webserver keystores alone. + +* A "sun.security.validator.ValidatorException: No trusted certificate + found" indicates the cacerts is not being used or it did not correctly + import the certificate. To rule out your web container replacing or in + some way modifying the trust manager, set the + CasProxyTicketValidator.trustStore property to the full file system + location to your cacerts file. + +* If your web container is ignoring your cacerts file, double-check it + is stored in $JAVA_HOME\lib\security\cacerts. $JAVA_HOME might be + pointing to the SDK, not JRE. In that case, copy + $JAVA_HOME\jre\lib\security\cacerts to $JAVA_HOME\lib\security\cacerts + diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..f293b34 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + +