1
0
mirror of synced 2026-08-06 10:18:52 +00:00

Use xml / javaconfig folders for samples

Fixes gh-3752
This commit is contained in:
Joe Grandja
2016-04-11 10:47:06 -04:00
committed by Rob Winch
parent 2c85fb05d0
commit 945a21a3fb
543 changed files with 828 additions and 430 deletions
+9
View File
@@ -0,0 +1,9 @@
dependencies {
compile project(':spring-security-core')
aspectpath project(':spring-security-aspects')
runtime project(':spring-security-config'),
project(':spring-security-aspects')
}
+130
View File
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-samples-xml-aspectj</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<name>spring-security-samples-xml-aspectj</name>
<description>spring-security-samples-xml-aspectj</description>
<url>http://spring.io/spring-security</url>
<organization>
<name>spring.io</name>
<url>http://spring.io/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@gopivotal.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-projects/spring-security</connection>
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
<url>https://github.com/spring-projects/spring-security</url>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.4</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-aspects</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,33 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.aspectj;
import org.springframework.security.access.annotation.Secured;
/**
* Service which is secured on the class level
*
* @author Mike Wiesner
* @since 3.0
*/
@Secured("ROLE_USER")
public class SecuredService {
public void secureMethod() {
// nothing
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.aspectj;
import org.springframework.security.access.annotation.Secured;
/**
* Service which is secured on method level
*
* @author Mike Wiesner
* @since 1.0
*/
public class Service {
@Secured("ROLE_USER")
public void secureMethod() {
// nothing
}
public void publicMethod() {
// nothing
}
}
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<sec:global-method-security secured-annotations="enabled" mode="aspectj" />
<!--
<bean id="aspectJSecurityInterceptor"
class="org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="securityMetadataSource">
<bean
class="org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource" />
</property>
</bean>
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<bean
class="org.springframework.security.authentication.TestingAuthenticationProvider" />
</property>
</bean>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean
class="org.springframework.security.access.vote.RoleVoter" />
</list>
</property>
</bean>
<bean
class="org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect"
factory-method="aspectOf">
<property name="securityInterceptor" ref="aspectJSecurityInterceptor" />
</bean>
-->
<bean class="sample.aspectj.Service" />
<bean class="sample.aspectj.SecuredService" />
</beans>
@@ -0,0 +1,96 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.aspectj;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
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.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:aspectj-context.xml")
public class AspectJInterceptorTests {
private Authentication admin = new UsernamePasswordAuthenticationToken("test", "xxx",
AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
private Authentication user = new UsernamePasswordAuthenticationToken("test", "xxx",
AuthorityUtils.createAuthorityList("ROLE_USER"));
@Autowired
private Service service;
@Autowired
private SecuredService securedService;
@Test
public void testPublicMethod() throws Exception {
service.publicMethod();
}
@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void testSecuredMethodNotAuthenticated() throws Exception {
service.secureMethod();
}
@Test(expected = AccessDeniedException.class)
public void testSecuredMethodWrongRole() throws Exception {
SecurityContextHolder.getContext().setAuthentication(admin);
service.secureMethod();
}
@Test
public void testSecuredMethodEverythingOk() throws Exception {
SecurityContextHolder.getContext().setAuthentication(user);
service.secureMethod();
}
@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void testSecuredClassNotAuthenticated() throws Exception {
securedService.secureMethod();
}
@Test(expected = AccessDeniedException.class)
public void testSecuredClassWrongRole() throws Exception {
SecurityContextHolder.getContext().setAuthentication(admin);
securedService.secureMethod();
}
@Test(expected = AccessDeniedException.class)
public void testSecuredClassWrongRoleOnNewedInstance() throws Exception {
SecurityContextHolder.getContext().setAuthentication(admin);
new SecuredService().secureMethod();
}
@Test
public void testSecuredClassEverythingOk() throws Exception {
SecurityContextHolder.getContext().setAuthentication(user);
securedService.secureMethod();
new SecuredService().secureMethod();
}
@After
public void tearDown() {
SecurityContextHolder.clearContext();
}
}
@@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.security" level="${sec.log.level}:-WARN"/>
<root level="${root.level}:-WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
+12
View File
@@ -0,0 +1,12 @@
To run a CAS server and client application, just execute the command
./gradlew cas
from the project root directory. You should then be able to point your browser at
https://localhost:8443/cas-sample/
to view the sample application. On attempting to access a secure page,
you'll be redirected to the CAS server where you can log in with one of
the usernames from the sample application context (enter the username in the
password field too, to authenticate to CAS in testing mode).
+129
View File
@@ -0,0 +1,129 @@
// CAS sample build file
apply plugin: 'war'
apply plugin: 'jetty'
apply plugin: 'groovy'
def excludeModules = ['spring-security-acl', 'jsr250-api', 'spring-jdbc', 'spring-tx']
def jettyVersion = '8.1.9.v20130131'
def keystore = "$rootDir/samples/certificates/server.jks"
def password = 'password'
configurations {
casServer
excludeModules.each {name ->
runtime.exclude module: name
}
runtime.exclude group: 'org.aspectj'
}
sourceSets {
test.resources.exclude 'GebConfig.groovy'
integrationTest.groovy.srcDir file('src/integration-test/groovy')
}
eclipse.classpath.plusConfigurations += [configurations.integrationTestRuntime]
dependencies {
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
compile project(':spring-security-core'),
project(':spring-security-cas'),
"org.jasig.cas.client:cas-client-core:$casClientVersion"
runtime project(':spring-security-web'),
project(':spring-security-config'),
"org.springframework:spring-context-support:$springVersion",
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
"ch.qos.logback:logback-classic:$logbackVersion",
"net.sf.ehcache:ehcache:$ehcacheVersion"
integrationTestCompile project(':spring-security-cas'),
"org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion",
"org.gebish:geb-spock:$gebVersion",
'commons-httpclient:commons-httpclient:3.1',
"org.eclipse.jetty:jetty-server:$jettyVersion",
"org.eclipse.jetty:jetty-servlet:$jettyVersion",
"org.codehaus.groovy:groovy:$groovyVersion",
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
spockDependencies
}
[jettyRun, jettyRunWar]*.configure {
contextPath = "/cas-sample"
def httpConnector = jettyRunWar.class.classLoader.loadClass('org.mortbay.jetty.nio.SelectChannelConnector').newInstance()
httpConnector.port = 8080
httpConnector.confidentialPort = 8443
def httpsConnector = jettyRunWar.class.classLoader.loadClass('org.mortbay.jetty.security.SslSocketConnector').newInstance()
httpsConnector.port = 8443
httpsConnector.keystore = httpsConnector.truststore = keystore
httpsConnector.keyPassword = httpsConnector.trustPassword = password
connectors = [httpConnector, httpsConnector]
doFirst() {
System.setProperty('cas.server.host', casServer().httpsHost)
System.setProperty('cas.service.host', jettyRunWar.httpsHost)
}
}
task cas (dependsOn: [jettyRunWar]) {
jettyRunWar.dependsOn(':spring-security-samples-xml-casserver:casServer')
}
task casServer(dependsOn: ':spring-security-samples-xml-casserver:casServer') {
}
integrationTest.dependsOn cas
integrationTest.doFirst {
def casServiceHost = jettyRunWar.httpsHost
systemProperties['cas.server.host'] = casServer().httpsHost
systemProperties['cas.service.host'] = casServiceHost
systemProperties['geb.build.baseUrl'] = 'https://'+casServiceHost+'/cas-sample/'
systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
systemProperties['jar.path'] = jar.archivePath
systemProperties['javax.net.ssl.trustStore'] = keystore
systemProperties['javax.net.ssl.trustStorePassword'] = password
}
gradle.taskGraph.whenReady {graph ->
def casServer = casServer()
[casServer,jettyRunWar]*.metaClass*.getHttpsConnector {->
def sslSocketConnClass = jettyRunWar.class.classLoader.loadClass('org.mortbay.jetty.security.SslSocketConnector')
delegate.connectors.find { it in sslSocketConnClass }
}
[casServer,jettyRunWar]*.metaClass*.getHttpsHost {->
"localhost:"+delegate.httpsConnector.port
}
jettyRunWar.metaClass.getHttpConnector {->
def channelConnClass = jettyRunWar.class.classLoader.loadClass('org.mortbay.jetty.nio.SelectChannelConnector')
delegate.connectors.find { it in channelConnClass }
}
if (graph.hasTask(cas)) {
casServer.daemon = true
}
if(graph.hasTask(integrationTest)) {
tasks.getByPath(':spring-security-samples-xml-casserver:casServerOverlay').logLevel = 'ERROR'
jettyRunWar {
additionalRuntimeJars += file("src/integration-test/resources")
daemon = true
}
[jettyRunWar.httpConnector,jettyRunWar.httpsConnector,casServer.httpsConnector]*.metaClass*.reservePort { taskToCloseSocket ->
def serverSocket = new ServerSocket(0)
delegate.metaClass.serverSocket = serverSocket
delegate.port = serverSocket.localPort
taskToCloseSocket.doFirst {
serverSocket.close()
}
}
[jettyRunWar.httpConnector,jettyRunWar.httpsConnector]*.reservePort(jettyRunWar)
jettyRunWar.httpConnector.confidentialPort = jettyRunWar.httpsConnector.port
casServer.httpsConnector.reservePort(casServer)
}
}
def casServer() {
tasks.getByPath(':spring-security-samples-xml-casserver:casServer')
}
+223
View File
@@ -0,0 +1,223 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-samples-xml-cassample</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-security-samples-xml-cassample</name>
<description>spring-security-samples-xml-cassample</description>
<url>http://spring.io/spring-security</url>
<organization>
<name>spring.io</name>
<url>http://spring.io/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@gopivotal.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-projects/spring-security</connection>
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
<url>https://github.com/spring-projects/spring-security</url>
</scm>
<properties>
<m2eclipse.wtp.contextRoot>/spring-security-samples-xml-cassample</m2eclipse.wtp.contextRoot>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.4.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.9.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>8.1.9.v20130131</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>8.1.9.v20130131</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.gebish</groupId>
<artifactId>geb-spock</artifactId>
<version>0.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-htmlunit-driver</artifactId>
<version>2.44.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>0.7-groovy-2.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit-dep</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>0.7-groovy-2.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit-dep</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,30 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas;
import java.io.File;
import geb.spock.*
/**
* Base test for Geb testing.
*
* @author Rob Winch
*/
class AbstractCasTests extends GebReportingSpec {
}
@@ -0,0 +1,122 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas
import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.methods.GetMethod
import org.jasig.cas.client.jaas.CasLoginModule;
import org.jasig.cas.client.proxy.Cas20ProxyRetriever
import org.springframework.security.samples.cas.pages.*
import spock.lang.*
/**
* Tests authenticating to the CAS Sample application using Proxy Tickets. Geb is used to authenticate the {@link JettyCasService}
* to the CAS Server in order to obtain the Ticket Granting Ticket. Afterwards HttpClient is used for accessing the CAS Sample application
* using Proxy Tickets obtained using the Proxy Granting Ticket.
*
* @author Rob Winch
*/
@Stepwise
class CasSampleProxyTests extends AbstractCasTests {
HttpClient client = new HttpClient()
@Shared String casServerUrl = LoginPage.url.replaceFirst('/login','')
@Shared JettyCasService service = new JettyCasService().init(casServerUrl)
@Shared Cas20ProxyRetriever retriever = new Cas20ProxyRetriever(casServerUrl,'UTF-8')
@Shared String pt
def cleanupSpec() {
service.stop()
}
def 'access secure page succeeds with ROLE_USER'() {
setup: 'Obtain a pgt for a user with ROLE_USER'
driver.get LoginPage.url+"?service="+service.serviceUrl()
at LoginPage
login 'scott'
when: 'User with ROLE_USER accesses the secure page'
def content = getSecured(getBaseUrl()+SecurePage.url).responseBodyAsString
then: 'The secure page is returned'
content.contains('<h1>Secure Page</h1>')
}
def 'access proxy ticket sample succeeds with ROLE_USER'() {
when: 'a proxy ticket is used to create another proxy ticket'
def content = getSecured(getBaseUrl()+ProxyTicketSamplePage.url).responseBodyAsString
then: 'The proxy ticket sample page is returned'
content.contains('<h1>Secure Page using a Proxy Ticket</h1>')
}
def 'access extremely secure page with ROLE_USER is denied'() {
when: 'User with ROLE_USER accesses the extremely secure page'
GetMethod method = getSecured(getBaseUrl()+ExtremelySecurePage.url)
then: 'access is denied'
assert method.responseBodyAsString =~ /(?i)403.*?Denied/
assert 403 == method.statusCode
}
def 'access secure page with ROLE_SUPERVISOR succeeds'() {
setup: 'Obtain pgt for user with ROLE_SUPERVISOR'
to LocalLogoutPage
casServerLogout.click()
driver.get(LoginPage.url+"?service="+service.serviceUrl())
at LoginPage
login 'rod'
when: 'User with ROLE_SUPERVISOR accesses the secure page'
def content = getSecured(getBaseUrl()+ExtremelySecurePage.url).responseBodyAsString
then: 'The secure page is returned'
content.contains('<h1>VERY Secure Page</h1>')
}
def 'access extremely secure page with ROLE_SUPERVISOR reusing pt succeeds (stateless mode works)'() {
when: 'User with ROLE_SUPERVISOR accesses extremely secure page with used pt'
def content = getSecured(getBaseUrl()+ExtremelySecurePage.url,pt).responseBodyAsString
then: 'The extremely secure page is returned'
content.contains('<h1>VERY Secure Page</h1>')
}
def 'access secure page with invalid proxy ticket fails'() {
when: 'Invalid ticket is used to access secure page'
GetMethod method = getSecured(getBaseUrl()+SecurePage.url,'invalidticket')
then: 'Authentication fails'
method.statusCode == 401
}
/**
* Gets the result of calling a url with a proxy ticket
* @param targetUrl the absolute url to attempt to access
* @param pt the proxy ticket to use. Defaults to {@link #getPt(String)} with targetUrl specified for the targetUrl.
* @return the GetMethod after calling a url with a specified proxy ticket
*/
GetMethod getSecured(String targetUrl,String pt=getPt(targetUrl)) {
assert pt != null
GetMethod method = new GetMethod(targetUrl+"?ticket="+pt)
int status = client.executeMethod(method)
method
}
/**
* Obtains a proxy ticket using the pgt from the {@link #service}.
* @param targetService the targetService that the proxy ticket will be valid for
* @return a proxy ticket for targetService
*/
String getPt(String targetService) {
assert service.pgt != null
pt = retriever.getProxyTicketIdFor(service.pgt, targetService)
pt
}
}
@@ -0,0 +1,135 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas
import geb.spock.*
import org.apache.http.impl.conn.DefaultClientConnectionOperator;
import org.junit.runner.RunWith;
import org.spockframework.runtime.Sputnik;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.core.context.ThreadLocalSecurityContextHolderStrategy;
import org.springframework.security.samples.cas.pages.*
import spock.lang.Shared;
import spock.lang.Stepwise;
/**
* Tests the CAS sample application using service tickets.
*
* @author Rob Winch
*/
@Stepwise
class CasSampleTests extends AbstractCasTests {
@Shared String casServerLogoutUrl = LoginPage.url.replaceFirst('/login','/logout')
def 'access home page with unauthenticated user succeeds'() {
when: 'Unauthenticated user accesses the Home Page'
to HomePage
then: 'The home page succeeds'
at HomePage
}
def 'access extremely secure page with unauthenitcated user requires login'() {
when: 'Unauthenticated user accesses the extremely secure page'
via ExtremelySecurePage
then: 'The login page is displayed'
at LoginPage
}
def 'authenticate attempt with invaid ticket fails'() {
when: 'present invalid ticket'
go "login/cas?ticket=invalid"
then: 'the login failed page is displayed'
$("h2").text() == 'Login to CAS failed!'
}
def 'access secure page with unauthenticated user requires login'() {
when: 'Unauthenticated user accesses the secure page'
via SecurePage
then: 'The login page is displayed'
at LoginPage
}
def 'saved request is used for secure page'() {
when: 'login with ROLE_USER after requesting the secure page'
login 'scott'
then: 'the secure page is displayed'
at SecurePage
}
def 'access proxy ticket sample with ROLE_USER is allowed'() {
when: 'user with ROLE_USER requests the proxy ticket sample page'
to ProxyTicketSamplePage
then: 'the proxy ticket sample page is displayed'
at ProxyTicketSamplePage
}
def 'access extremely secure page with ROLE_USER is denied'() {
when: 'User with ROLE_USER accesses extremely secure page'
via ExtremelySecurePage
then: 'the access denied page is displayed'
at AccessDeniedPage
}
def 'clicking local logout link displays local logout page'() {
setup: 'Navigate to page with logout link'
to SecurePage
when: 'Local logout link is clicked'
navModule.logout.click()
then: 'the local logout page is displayed'
at LocalLogoutPage
}
def 'clicking cas server logout link successfully performs logout'() {
when: 'the cas server logout link is clicked and the secure page is requested'
casServerLogout.click()
via SecurePage
then: 'the login page is displayed'
at LoginPage
}
def 'access extremely secure page with ROLE_SUPERVISOR succeeds'() {
setup: 'login with ROLE_SUPERVISOR'
login 'rod'
when: 'access extremely secure page'
to ExtremelySecurePage
then: 'extremely secure page is displayed'
at ExtremelySecurePage
}
def 'after logout extremely secure page requires login'() {
when: 'logout and request extremely secure page'
navModule.logout.click()
casServerLogout.click()
via ExtremelySecurePage
then: 'login page is displayed'
at LoginPage
}
def 'logging out of the cas server successfully logs out of the cas sample application'() {
setup: 'login with ROLE_USER'
via SecurePage
at LoginPage
login 'rod'
at SecurePage
when: 'logout of the CAS Server'
go casServerLogoutUrl
via SecurePage
then: 'user is logged out of the CAS Service'
at LoginPage
}
}
@@ -0,0 +1,124 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas;
import java.io.IOException
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.methods.GetMethod;
import org.eclipse.jetty.server.Request
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.AbstractHandler
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
import org.jasig.cas.client.validation.Assertion
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
/**
* A CAS Service that allows a PGT to be obtained. This is useful for testing use of proxy tickets.
*
* @author Rob Winch
*/
class JettyCasService extends Server {
private Cas20ProxyTicketValidator validator
private int port = availablePort()
/**
* The Proxy Granting Ticket. To initialize pgt, authenticate to the CAS Server with the service parameter
* equal to {@link #serviceUrl()}.
*/
String pgt
/**
* Start the CAS Service which will be available at {@link #serviceUrl()}.
*
* @param casServerUrl
* @return
*/
def init(String casServerUrl) {
ProxyGrantingTicketStorage storage = new ProxyGrantingTicketStorageImpl()
validator = new Cas20ProxyTicketValidator(casServerUrl)
validator.setAcceptAnyProxy(true)
validator.setProxyGrantingTicketStorage(storage)
validator.setProxyCallbackUrl(absoluteUrl('callback'))
String password = System.getProperty('javax.net.ssl.trustStorePassword','password')
SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector()
ssl_connector.setPort(port)
ssl_connector.setKeystore(getTrustStore())
ssl_connector.setPassword(password)
ssl_connector.setKeyPassword(password)
addConnector(ssl_connector)
setHandler(new AbstractHandler() {
public void handle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
def st = request.getParameter('ticket')
if(st) {
JettyCasService.this.validator.validate(st, JettyCasService.this.serviceUrl())
}
def pgt = request.getParameter('pgtId')
if(pgt) {
JettyCasService.this.pgt = pgt
}
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
}
})
start()
this
}
/**
* Returns the absolute URL that this CAS service is available at.
* @return
*/
String serviceUrl() {
absoluteUrl('service')
}
/**
* Given a relative url, will provide an absolute url for this CAS Service.
* @param relativeUrl the relative url (i.e. service, callback, etc)
* @return
*/
private String absoluteUrl(String relativeUrl) {
"https://localhost:${port}/${relativeUrl}"
}
private static String getTrustStore() {
String trustStoreLocation = System.getProperty('javax.net.ssl.trustStore')
if(trustStoreLocation == null || !new File(trustStoreLocation).isFile()) {
throw new IllegalStateException('Could not find the trust store at path "'+trustStoreLocation+'". Specify the location using the javax.net.ssl.trustStore system property.')
}
trustStoreLocation
}
/**
* Obtains a random available port (i.e. one that is not in use)
* @return
*/
private static int availablePort() {
ServerSocket server = new ServerSocket(0)
int port = server.localPort
server.close()
port
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.modules;
import geb.*
import org.springframework.security.samples.cas.pages.*
/**
* Represents the navigation for the CAS Sample application
*
* @author Rob Winch
*/
class NavModule extends Module {
static content = {
home(to: HomePage) { $("a", text: "Home") }
logout(to: LocalLogoutPage) { $("a", text: "Logout") }
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.pages;
import geb.*
/**
* Represents the access denied page
*
* @author Rob Winch
*/
class AccessDeniedPage extends Page {
static at = { $("*",text: iContains(~/.*?403.*/)) }
}
@@ -0,0 +1,32 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.pages;
import geb.*
import org.springframework.security.samples.cas.modules.*
/**
* Represents the extremely secure page of the CAS Sample application.
*
* @author Rob Winch
*/
class ExtremelySecurePage extends Page {
static url = "secure/extreme/"
static at = { assert $('h1').text() == 'VERY Secure Page'; true; }
static content = {
navModule { module NavModule }
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.pages
import geb.*
/**
* Represents the Home page of the CAS sample application
*
* @author Rob Winch
*/
class HomePage extends Page {
static at = { assert $('h1').text() == 'Home Page'; true}
static url = ''
static content = {
securePage { $('a',text: 'Secure page') }
extremelySecurePage { $('a',text: 'Extremely secure page') }
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.pages;
import geb.*
/**
* This represents the local logout page. This page is where the user is logged out of the CAS Sample application, but
* since the user is still logged into the CAS Server accessing a protected page within the CAS Sample application would result
* in SSO occurring again. To fully logout, the user should click the cas server logout url which logs out of the cas server and performs
* single logout on the other services.
*
* @author Rob Winch
*/
class LocalLogoutPage extends Page {
static url = 'cas-logout.jsp'
static at = { assert driver.currentUrl.endsWith(url); true }
static content = {
casServerLogout { $('a',text: 'Logout of CAS') }
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.pages;
import geb.*
/**
* The CAS login page.
*
* @author Rob Winch
*/
class LoginPage extends Page {
static url = loginUrl()
static at = { assert driver.currentUrl.startsWith(loginUrl()); true}
static content = {
login(required:false) { user, password=user ->
loginForm.username = user
loginForm.password = password
submit.click()
}
loginForm { $('#login') }
submit { $('input', type: 'submit') }
}
/**
* Gets the login page url which might change based upon the system properties. This is to support using a randomly available port for CI.
* @return
*/
private static String loginUrl() {
def host = System.getProperty('cas.server.host', 'localhost:9443')
"https://${host}/cas/login"
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.pages;
import geb.*
import org.springframework.security.samples.cas.modules.*
/**
* Represents the proxy ticket sample page within the CAS Sample application.
*
* @author Rob Winch
*/
class ProxyTicketSamplePage extends Page {
static url = "secure/ptSample"
static at = { assert $('h1').text() == 'Secure Page using a Proxy Ticket'; true}
static content = {
navModule { module NavModule }
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.cas.pages;
import geb.*
import org.springframework.security.samples.cas.modules.*
/**
* Represents the secure page within the CAS Sample application.
*
* @author Rob Winch
*/
class SecurePage extends Page {
static url = "secure/"
static at = { assert $('h1').text() == 'Secure Page'; true}
static content = {
navModule { module NavModule }
}
}
@@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -0,0 +1,89 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* 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.cas.web;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jasig.cas.client.util.CommonUtils;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
/**
* <p>
* {@link ProxyTicketSampleServlet} demonstrates how to obtain a proxy ticket and then use
* it to make a remote call. To learn how proxy tickets work, see the <a
* href="https://wiki.jasig.org/display/CAS/Proxy+CAS+Walkthrough">Proxy CAS
* Walkthrough</a>
* </p>
*
* @author Rob Winch
*/
public final class ProxyTicketSampleServlet extends HttpServlet {
/**
* This is the URL that will be called and authenticate a proxy ticket.
*/
private String targetUrl;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
final CasAuthenticationToken token = (CasAuthenticationToken) request
.getUserPrincipal();
// proxyTicket could be reused to make calls to to the CAS service even if the
// target url differs
final String proxyTicket = token.getAssertion().getPrincipal()
.getProxyTicketFor(targetUrl);
// Make a remote call to ourself. This is a bit silly, but it works well to
// demonstrate how to use proxy tickets.
final String serviceUrl = targetUrl + "?ticket="
+ URLEncoder.encode(proxyTicket, "UTF-8");
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
// modify the response and write it out to inform the user that it was obtained
// using a proxy ticket.
proxyResponse = proxyResponse.replaceFirst("Secure Page",
"Secure Page using a Proxy Ticket");
proxyResponse = proxyResponse.replaceFirst("<p>", "<p>This page is rendered by "
+ getClass().getSimpleName()
+ " by making a remote call to the Secure Page using a proxy ticket ("
+ proxyTicket + ") and inserts this message. ");
final PrintWriter writer = response.getWriter();
writer.write(proxyResponse);
}
/**
* Initialize the target URL. It allows for the host to change based upon the
* "cas.service.host" system property. If the property is not set, the default is
* "localhost:8443".
*/
@Override
public void init() throws ServletException {
super.init();
String casServiceHost = System.getProperty("cas.service.host", "localhost:8443");
targetUrl = "https://" + casServiceHost + "/cas-sample/secure/";
}
private static final long serialVersionUID = -7720161771819727775L;
}
@@ -0,0 +1,8 @@
<html>
<head>
<title>403 - Access Denied</title>
</head>
<body>
<h1>403 - Access Denied</h1>
</body>
</html>
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns="http://www.springframework.org/schema/security"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<http entry-point-ref="casEntryPoint">
<intercept-url pattern="/" access="permitAll"/>
<intercept-url pattern="/index.jsp" access="permitAll"/>
<intercept-url pattern="/cas-logout.jsp" access="permitAll"/>
<intercept-url pattern="/casfailed.jsp" access="permitAll"/>
<intercept-url pattern="/secure/extreme/**"
access="hasRole('ROLE_SUPERVISOR')" />
<intercept-url pattern="/secure/**" access="hasRole('ROLE_USER')" />
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
<custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
<custom-filter ref="casFilter" position="CAS_FILTER" />
<logout logout-success-url="/cas-logout.jsp"/>
<csrf disabled="true"/>
</http>
<authentication-manager alias="authManager">
<authentication-provider ref="casAuthProvider" />
</authentication-manager>
<user-service id="userService">
<user name="rod" password="rod" authorities="ROLE_SUPERVISOR,ROLE_USER" />
<user name="dianne" password="dianne" authorities="ROLE_USER" />
<user name="scott" password="scott" authorities="ROLE_USER" />
</user-service>
<!-- This filter handles a Single Logout Request from the CAS Server -->
<b:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>
<!-- This filter redirects to the CAS Server to signal Single Logout should be performed -->
<b:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"
p:filterProcessesUrl="/logout/cas">
<b:constructor-arg value="https://${cas.server.host}/cas/logout"/>
<b:constructor-arg>
<b:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</b:constructor-arg>
</b:bean>
<b:bean id="serviceProperties"
class="org.springframework.security.cas.ServiceProperties"
p:service="https://${cas.service.host}/cas-sample/login/cas"
p:authenticateAllArtifacts="true"/>
<b:bean id="casEntryPoint"
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"
p:serviceProperties-ref="serviceProperties" p:loginUrl="https://${cas.server.host}/cas/login" />
<b:bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter"
p:authenticationManager-ref="authManager"
p:serviceProperties-ref="serviceProperties"
p:proxyGrantingTicketStorage-ref="pgtStorage"
p:proxyReceptorUrl="/login/cas/proxyreceptor">
<b:property name="authenticationDetailsSource">
<b:bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
<b:constructor-arg ref="serviceProperties"/>
</b:bean>
</b:property>
<b:property name="authenticationFailureHandler">
<b:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"
p:defaultFailureUrl="/casfailed.jsp"/>
</b:property>
</b:bean>
<!--
NOTE: In a real application you should not use an in memory implementation. You will also want
to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup()
-->
<b:bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
<b:bean id="casAuthProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"
p:serviceProperties-ref="serviceProperties"
p:key="casAuthProviderKey">
<b:property name="authenticationUserDetailsService">
<b:bean
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<b:constructor-arg ref="userService" />
</b:bean>
</b:property>
<b:property name="ticketValidator">
<b:bean
class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"
p:acceptAnyProxy="true"
p:proxyCallbackUrl="https://${cas.service.host}/cas-sample/login/cas/proxyreceptor"
p:proxyGrantingTicketStorage-ref="pgtStorage">
<b:constructor-arg value="https://${cas.server.host}/cas" />
</b:bean>
</b:property>
<b:property name="statelessTicketCache">
<b:bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
<b:property name="cache">
<b:bean id="ehcache" class="net.sf.ehcache.Cache"
init-method="initialise"
destroy-method="dispose">
<b:constructor-arg value="casTickets"/>
<b:constructor-arg value="50"/>
<b:constructor-arg value="true"/>
<b:constructor-arg value="false"/>
<b:constructor-arg value="3600"/>
<b:constructor-arg value="900"/>
<b:property name="cacheManager">
<b:bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
</b:property>
</b:bean>
</b:property>
</b:bean>
</b:property>
</b:bean>
<!-- Configuration for the environment can be overriden by system properties -->
<context:property-placeholder system-properties-mode="OVERRIDE" properties-ref="environment"/>
<util:properties id="environment">
<b:prop key="cas.service.host">localhost:8443</b:prop>
<b:prop key="cas.server.host">localhost:9443</b:prop>
</util:properties>
</b:beans>
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Tutorial web application
-
-->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Spring Security CAS Demo Application</display-name>
<!--
- Location of the XML file that defines the root application context
- Applied by ContextLoaderListener.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-security.xml
</param-value>
</context-param>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>cas.root</param-value>
</context-param>
<!--
Include the character encoding Filter as per JASIG recommenation when doing Single Sign Out
https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out
-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
Included to support Single Logout. Note that the SingleSignOutFilter is included in the
springSecurityFilterChain. However, it could also be placed as the first filter-mapping
in the web.xml
-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!--
- Loads the root application context of this web app at startup.
- The application context is then available via
- WebApplicationContextUtils.getWebApplicationContext(servletContext).
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>ptSampleServlet</servlet-name>
<servlet-class>org.springframework.security.samples.cas.web.ProxyTicketSampleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ptSampleServlet</servlet-name>
<url-pattern>/secure/ptSample</url-pattern>
</servlet-mapping>
<error-page>
<error-code>403</error-code>
<location>/403.jsp</location>
</error-page>
</web-app>
@@ -0,0 +1,15 @@
<html>
<head>
<title>Single-sign out?</title>
</head>
<body>
<h2>Do you want to log out of CAS?</h2>
<p>You have logged out of this application, but may still have an active single-sign on session with CAS.</p>
<p><a href="logout/cas">Logout of CAS</a></p>
</body>
</html>
@@ -0,0 +1,17 @@
<%@ page import="org.springframework.security.core.AuthenticationException" %>
<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" %>
<html>
<head>
<title>Login to CAS failed!</title>
</head>
<body>
<h2>Login to CAS failed!</h2>
<font color="red">
Your CAS credentials were rejected.<br/>
</font>
</body>
</html>
@@ -0,0 +1,12 @@
<html>
<body>
<h1>Home Page</h1>
<p>Anyone can view this page.</p>
<p>Your principal object is....: <%= request.getUserPrincipal() %></p>
<p><a href="secure/index.jsp">Secure page</a></p>
<p><a href="secure/ptSample">Proxy Ticket Sample page</a></p>
<p><a href="secure/extreme/index.jsp">Extremely secure page</a></p>
</body>
</html>
@@ -0,0 +1,12 @@
<html>
<body>
<h1>VERY Secure Page</h1>
This is a protected page. You can only see me if you are a supervisor.
<p><a href="../../">Home</a>
<p><a href="../../secure/index.jsp">Secure page</a></p>
<p><a href="../../secure/ptSample">Proxy Ticket Sample page</a></p>
<p><a href="../../logout">Logout</a>
<
</body>
</html>
@@ -0,0 +1,15 @@
<html>
<body>
<h1>Secure Page</h1>
<p>This is a protected page. You can get to me if you've been remembered,
or if you've authenticated this session.</p>
<%if (request.isUserInRole("ROLE_SUPERVISOR")) { %>
<p>You are a supervisor! You can therefore see the <a href="extreme/index.jsp">extremely secure page</a>.</p>
<% } %>
<p><a href="../">Home</a>
<p><a href="ptSample">Proxy Ticket Sample page</a></p>
<p><a href="../logout">Logout</a>
</body>
</html>
@@ -0,0 +1,63 @@
import org.apache.tools.ant.filters.ReplaceTokens
apply plugin: 'jetty'
def keystore = "$rootDir/samples/certificates/server.jks"
def password = 'password'
configurations {
casServer
}
dependencies {
casServer "org.jasig.cas:cas-server-webapp:4.0.0@war"
}
task casServerOverlay(type: Sync) {
def war = configurations.casServer.resolve().toArray()[0]
def warName = war.name.replace('.war','-custom')
def overlayDir = file('src/main/webapp')
def explodedWar = file("$buildDir/tmp/${warName}")
ext.customWar = file("$buildDir/tmp/${warName}.war")
ext.tokens = [logLevel: 'INFO']
inputs.files(war, overlayDir)
inputs.property('tokens',{tokens})
outputs.files (customWar,explodedWar,file("$buildDir/tmp/expandedArchives"))
from zipTree(war)
from (overlayDir) {
filter(ReplaceTokens,tokens: tokens)
}
into explodedWar
doLast {
if(customWar.exists()) {
customWar.delete()
}
ant.zip(destfile: customWar, baseDir: explodedWar)
}
}
casServerOverlay.metaClass.setLogLevel { level ->
tokens['logLevel'] = level
}
task casServer (type: org.gradle.api.plugins.jetty.JettyRunWar, dependsOn: 'casServerOverlay') {
contextPath = "/cas"
connectors = [casServer.class.classLoader.loadClass('org.mortbay.jetty.security.SslSocketConnector').newInstance()]
connectors[0].port = 9443
connectors[0].keystore = connectors[0].truststore = keystore
connectors[0].keyPassword = connectors[0].trustPassword = password
connectors[0].wantClientAuth = true
connectors[0].needClientAuth = false
webApp = casServerOverlay.customWar
inputs.file casServerOverlay.customWar
doFirst() {
System.setProperty('javax.net.ssl.trustStore', keystore)
System.setProperty('javax.net.ssl.trustStorePassword', password)
System.setProperty('java.naming.factory.url.pkgs','org.mortbay.naming')
System.setProperty('java.naming.factory.initial','org.mortbay.naming.InitialContextFactory')
}
}
+116
View File
@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-samples-xml-casserver</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-security-samples-xml-casserver</name>
<description>spring-security-samples-xml-casserver</description>
<url>http://spring.io/spring-security</url>
<organization>
<name>spring.io</name>
<url>http://spring.io/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@gopivotal.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-projects/spring-security</connection>
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
<url>https://github.com/spring-projects/spring-security</url>
</scm>
<properties>
<m2eclipse.wtp.contextRoot>/spring-security-samples-xml-casserver</m2eclipse.wtp.contextRoot>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false" xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %p [%c] - &lt;%m&gt;%n"/>
</layout>
</appender>
<logger name="org.jasig" additivity="true">
<level value="@logLevel@" />
</logger>
<root>
<level value="ERROR"/>
<appender-ref ref="console"/>
</root>
</log4j:configuration>
@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to Jasig under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Jasig licenses this file to you 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 the following location:
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.
-->
<!--
| deployerConfigContext.xml centralizes into one file some of the declarative configuration that
| all CAS deployers will need to modify.
|
| This file declares some of the Spring-managed JavaBeans that make up a CAS deployment.
| The beans declared in this file are instantiated at context initialization time by the Spring
| ContextLoaderListener declared in web.xml. It finds this file because this
| file is among those declared in the context parameter "contextConfigLocation".
|
| By far the most common change you will need to make in this file is to change the last bean
| declaration to replace the default authentication handler with
| one implementing your approach for authenticating usernames and passwords.
+-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--
| The authentication manager defines security policy for authentication by specifying at a minimum
| the authentication handlers that will be used to authenticate credential. While the AuthenticationManager
| interface supports plugging in another implementation, the default PolicyBasedAuthenticationManager should
| be sufficient in most cases.
+-->
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
<constructor-arg>
<map>
<!--
| IMPORTANT
| Every handler requires a unique name.
| If more than one instance of the same handler class is configured, you must explicitly
| set its name to something other than its default name (typically the simple class name).
-->
<entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
<entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />
</map>
</constructor-arg>
<!-- Uncomment the metadata populator to allow clearpass to capture and cache the password
This switch effectively will turn on clearpass.
<property name="authenticationMetaDataPopulators">
<util:list>
<bean class="org.jasig.cas.extension.clearpass.CacheCredentialsMetaDataPopulator"
c:credentialCache-ref="encryptedMap" />
</util:list>
</property>
-->
<!--
| Defines the security policy around authentication. Some alternative policies that ship with CAS:
|
| * NotPreventedAuthenticationPolicy - all credential must either pass or fail authentication
| * AllAuthenticationPolicy - all presented credential must be authenticated successfully
| * RequiredHandlerAuthenticationPolicy - specifies a handler that must authenticate its credential to pass
-->
<property name="authenticationPolicy">
<bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
</property>
</bean>
<!-- Required for proxy ticket mechanism. -->
<bean id="proxyAuthenticationHandler"
class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient" />
<!--
| TODO: Replace this component with one suitable for your enviroment.
|
| This component provides authentication for the kind of credential used in your environment. In most cases
| credential is a username/password pair that lives in a system of record like an LDAP directory.
| The most common authentication handler beans:
|
| * org.jasig.cas.authentication.LdapAuthenticationHandler
| * org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler
| * org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler
| * org.jasig.cas.support.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler
-->
<bean id="primaryAuthenticationHandler"
class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
<property name="users">
<map>
<entry key="scott" value="scott"/>
<entry key="rod" value="rod"/>
<entry key="dianne" value="dianne"/>
</map>
</property>
</bean>
<!-- Required for proxy ticket mechanism -->
<bean id="proxyPrincipalResolver"
class="org.jasig.cas.authentication.principal.BasicPrincipalResolver" />
<!--
| Resolves a principal from a credential using an attribute repository that is configured to resolve
| against a deployer-specific store (e.g. LDAP).
-->
<bean id="primaryPrincipalResolver"
class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
<property name="attributeRepository" ref="attributeRepository" />
</bean>
<!--
Bean that defines the attributes that a service may return. This example uses the Stub/Mock version. A real implementation
may go against a database or LDAP server. The id should remain "attributeRepository" though.
+-->
<bean id="attributeRepository" class="org.jasig.services.persondir.support.StubPersonAttributeDao"
p:backingMap-ref="attrRepoBackingMap" />
<util:map id="attrRepoBackingMap">
<entry key="uid" value="uid" />
<entry key="eduPersonAffiliation" value="eduPersonAffiliation" />
<entry key="groupMembership" value="groupMembership" />
</util:map>
<!--
Sample, in-memory data store for the ServiceRegistry. A real implementation
would probably want to replace this with the JPA-backed ServiceRegistry DAO
The name of this bean should remain "serviceRegistryDao".
+-->
<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"
p:registeredServices-ref="registeredServicesList" />
<util:list id="registeredServicesList">
<bean class="org.jasig.cas.services.RegexRegisteredService"
p:id="0" p:name="HTTP and IMAP" p:description="Allows HTTP(S) and IMAP(S) protocols"
p:serviceId="^(https?|imaps?)://.*" p:evaluationOrder="10000001"
p:allowedToProxy="true"/>
<!--
Use the following definition instead of the above to further restrict access
to services within your domain (including sub domains).
Note that example.com must be replaced with the domain you wish to permit.
This example also demonstrates the configuration of an attribute filter
that only allows for attributes whose length is 3.
-->
<!--
<bean class="org.jasig.cas.services.RegexRegisteredService">
<property name="id" value="1" />
<property name="name" value="HTTP and IMAP on example.com" />
<property name="description" value="Allows HTTP(S) and IMAP(S) protocols on example.com" />
<property name="serviceId" value="^(https?|imaps?)://([A-Za-z0-9_-]+\.)*example\.com/.*" />
<property name="evaluationOrder" value="0" />
<property name="attributeFilter">
<bean class="org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter" c:regex="^\w{3}$" />
</property>
</bean>
-->
</util:list>
<bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" />
<bean id="healthCheckMonitor" class="org.jasig.cas.monitor.HealthCheckMonitor" p:monitors-ref="monitorsList" />
<util:list id="monitorsList">
<bean class="org.jasig.cas.monitor.MemoryMonitor" p:freeMemoryWarnThreshold="10" />
<!--
NOTE
The following ticket registries support SessionMonitor:
* DefaultTicketRegistry
* JpaTicketRegistry
Remove this monitor if you use an unsupported registry.
-->
<bean class="org.jasig.cas.monitor.SessionMonitor"
p:ticketRegistry-ref="ticketRegistry"
p:serviceTicketCountWarnThreshold="5000"
p:sessionCountWarnThreshold="100000" />
</util:list>
</beans>
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to Jasig under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Jasig licenses this file to you 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 the following location:
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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<description>
This is the main Spring configuration file with some of the main "core" classes defined. You shouldn't really
modify this unless you
know what you're doing!
</description>
<!--
Including this aspectj-autoproxy element will cause spring to automatically
create proxies around any beans defined in this file that match the pointcuts
of any aspects defined in this file.
-->
<aop:aspectj-autoproxy/>
<!--
Declare the TimingAspect that we want to weave into the other beans
defined in this config file.
-->
<bean id="timingAspect" class="org.perf4j.log4j.aop.TimingAspect"/>
<!--
Message source for this context, loaded from localized "messages_xx" files.]
Disable the fallback mechanism to the system/JVM locale. By turning off this behavior, CAS
will be able to revert back to the default language bundle that is "messages.properties"
and will not rely on the JVM default locale which introduces the side effect of rendering
the UI in the JVM locale by default.
Also, explicitly set the default encoding to be UTF-8 when parsing message bundles.
The default, if not set, is none which forces ISO-8859-1 of java.util.ResourceBundle.
-->
<bean id="messageSource" class="org.jasig.cas.web.view.CasReloadableMessageBundle"
p:basenames-ref="basenames" p:fallbackToSystemLocale="false" p:defaultEncoding="UTF-8"
p:cacheSeconds="180" p:useCodeAsDefaultMessage="true" />
<util:list id="basenames">
<value>classpath:custom_messages</value>
<value>classpath:messages</value>
</util:list>
<bean id="servicesManager" class="org.jasig.cas.services.DefaultServicesManagerImpl"
c:serviceRegistryDao-ref="serviceRegistryDao" />
<!--
Job to periodically reload services from service registry.
This job is needed for a clustered CAS environment since service changes
in one CAS node are not known to the other until a reload.
-->
<bean id="serviceRegistryReloaderJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
p:targetObject-ref="servicesManager"
p:targetMethod="reload"/>
<bean id="periodicServiceRegistryReloaderTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"
p:jobDetail-ref="serviceRegistryReloaderJobDetail"
p:startDelay="${service.registry.quartz.reloader.startDelay:120000}"
p:repeatInterval="${service.registry.quartz.reloader.repeatInterval:120000}"/>
<bean id="noRedirectHttpClient" class="org.jasig.cas.util.SimpleHttpClient" parent="httpClient"
p:followRedirects="false" />
<bean id="persistentIdGenerator"
class="org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator"
p:salt="casrocks"/>
<bean id="logoutManager" class="org.jasig.cas.logout.LogoutManagerImpl"
c:servicesManager-ref="servicesManager"
c:httpClient-ref="noRedirectHttpClient"
c:logoutMessageBuilder-ref="logoutBuilder"
p:disableSingleSignOut="${slo.callbacks.disabled:false}" />
<bean id="logoutBuilder" class="org.jasig.cas.logout.SamlCompliantLogoutMessageCreator" />
<!-- CentralAuthenticationService -->
<bean id="centralAuthenticationService" class="org.jasig.cas.CentralAuthenticationServiceImpl">
<constructor-arg index="0" ref="ticketRegistry"/>
<constructor-arg index="1">
<null />
</constructor-arg>
<constructor-arg index="2" ref="authenticationManager"/>
<constructor-arg index="3" ref="ticketGrantingTicketUniqueIdGenerator"/>
<constructor-arg index="4" ref="uniqueIdGeneratorsMap"/>
<constructor-arg index="5" ref="grantingTicketExpirationPolicy"/>
<constructor-arg index="6" ref="serviceTicketExpirationPolicy"/>
<constructor-arg index="7" ref="servicesManager"/>
<constructor-arg index="8" ref="logoutManager"/>
<property name="persistentIdGenerator" ref="persistentIdGenerator"/>
</bean>
<bean id="proxy10Handler" class="org.jasig.cas.ticket.proxy.support.Cas10ProxyHandler"/>
<bean id="proxy20Handler" class="org.jasig.cas.ticket.proxy.support.Cas20ProxyHandler"
p:httpClient-ref="httpClient"
p:uniqueTicketIdGenerator-ref="proxy20TicketUniqueIdGenerator"/>
<!-- ADVISORS -->
<bean id="advisorAutoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean id="validationAnnotationBeanPostProcessor" class="org.jasig.cas.util.CustomBeanValidationPostProcessor"
p:afterInitialization="true" />
<!-- The scheduler bean wires up any triggers that define scheduled tasks -->
<bean id="scheduler" class="org.jasig.cas.util.AutowiringSchedulerFactoryBean"/>
<bean id="httpClient" class="org.jasig.cas.util.SimpleHttpClient"
p:readTimeout="5000"
p:connectionTimeout="5000">
<property name="executorService">
<bean class="org.springframework.core.task.support.ExecutorServiceAdapter">
<constructor-arg>
<bean class="org.springframework.core.task.SyncTaskExecutor"/>
</constructor-arg>
</bean>
</property>
</bean>
</beans>
+105
View File
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>cas</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<name>cas</name>
<description>cas</description>
<url>http://spring.io/spring-security</url>
<organization>
<name>spring.io</name>
<url>http://spring.io/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@gopivotal.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-projects/spring-security</connection>
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
<url>https://github.com/spring-projects/spring-security</url>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -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
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!--
- Contacts web application
- Client application context
-->
<beans>
<!-- Resolves ${...} placeholders from client.properties -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location"><value>client.properties</value></property>
</bean>
<!-- Proxy for the RMI-exported ContactManager -->
<!-- COMMENTED OUT BY DEFAULT TO AVOID CONFLICTS WITH APPLICATION SERVERS
<bean id="rmiProxy" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceInterface">
<value>sample.contact.ContactManager</value>
</property>
<property name="serviceUrl">
<value>rmi://${serverName}:${rmiPort}/contactManager</value>
</property>
<property name="remoteInvocationFactory">
<ref local="remoteInvocationFactory"/>
</property>
</bean>
<bean id="remoteInvocationFactory" class="org.springframework.security.ui.rmi.ContextPropagatingRemoteInvocationFactory"/>
-->
<!-- Proxy for the HTTP-invoker-exported ContactManager -->
<!-- Spring's HTTP invoker uses Java serialization via HTTP -->
<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceInterface">
<value>sample.contact.ContactManager</value>
</property>
<property name="serviceUrl">
<value>http://${serverName}:${httpPort}${contextPath}/remoting/ContactManager-httpinvoker</value>
</property>
<property name="httpInvokerRequestExecutor">
<ref local="httpInvokerRequestExecutor"/>
</property>
</bean>
<!-- Automatically propagates ContextHolder-managed Authentication principal
and credentials to a HTTP invoker BASIC authentication header -->
<bean id="httpInvokerRequestExecutor" class="org.springframework.security.core.context.httpinvoker.AuthenticationSimpleHttpInvokerRequestExecutor"/>
<!-- Proxy for the Hessian-exported ContactManager
<bean id="hessianProxy" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceInterface">
<value>sample.contact.ContactManager</value>
</property>
<property name="serviceUrl">
<value>http://${serverName}:${httpPort}${contextPath}/remoting/ContactManager-hessian</value>
</property>
</bean>
-->
<!-- Proxy for the Burlap-exported ContactManager
<bean id="burlapProxy" class="org.springframework.remoting.caucho.BurlapProxyFactoryBean">
<property name="serviceInterface">
<value>sample.contact.ContactManager</value>
</property>
<property name="serviceUrl">
<value>http://${serverName}:${httpPort}${contextPath}/remoting/ContactManager-burlap</value>
</property>
</bean>
-->
</beans>
+29
View File
@@ -0,0 +1,29 @@
// Contacts sample build file
apply from: WAR_SAMPLE_GRADLE
dependencies {
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
compile project(':spring-security-core'),
project(':spring-security-acl'),
"org.springframework:spring-aop:$springVersion",
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-jdbc:$springVersion",
"org.springframework:spring-tx:$springVersion",
"org.springframework:spring-web:$springVersion",
"org.springframework:spring-webmvc:$springVersion"
runtime project(':spring-security-web'),
project(':spring-security-config'),
project(':spring-security-taglibs'),
"org.springframework:spring-context-support:$springVersion",
jstlDependencies,
"org.hsqldb:hsqldb:$hsqlVersion",
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
"ch.qos.logback:logback-classic:$logbackVersion",
"net.sf.ehcache:ehcache:$ehcacheVersion"
integrationTestCompile gebDependencies
}
+270
View File
@@ -0,0 +1,270 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-samples-xml-contacts</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-security-samples-xml-contacts</name>
<description>spring-security-samples-xml-contacts</description>
<url>http://spring.io/spring-security</url>
<organization>
<name>spring.io</name>
<url>http://spring.io/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@gopivotal.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-projects/spring-security</connection>
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
<url>https://github.com/spring-projects/spring-security</url>
</scm>
<properties>
<m2eclipse.wtp.contextRoot>/sample</m2eclipse.wtp.contextRoot>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>javax.servlet.jsp.jstl-api</artifactId>
<version>1.2.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.9.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-jstlel</artifactId>
<version>1.2.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.gebish</groupId>
<artifactId>geb-spock</artifactId>
<version>0.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-htmlunit-driver</artifactId>
<version>2.44.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>0.7-groovy-2.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit-dep</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>0.7-groovy-2.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit-dep</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,87 @@
/*
* Copyright 2011 the original author or authors.
*
* 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
import geb.spock.*
import spock.lang.Stepwise
import org.springframework.security.samples.pages.*
/**
* Tests the CAS sample application using service tickets.
*
* @author Rob Winch
*/
@Stepwise
class ContactsTests extends GebReportingSpec {
def 'access home page with unauthenticated user success'() {
when: 'Unauthenticated user accesses the Home Page'
to HomePage
then: 'The page is displayed'
at HomePage
}
def 'access manage page with unauthenticated user sends to login page'() {
when: 'Unauthenticated user accesses the Manage Page'
manage.click(LoginPage)
then: 'The login page is displayed'
at LoginPage
}
def 'authenticated user is sent to original page'() {
when: 'user authenticates'
login()
then: 'The manage page is displayed'
at ContactsPage
}
def 'add contact link works'() {
when: 'user clicks add link'
addContact.click(AddPage)
then: 'The add page is displayed'
at AddPage
}
def 'add contact'() {
when: 'add a contact'
addContact
then: 'The add page is displayed'
at ContactsPage
and: 'The new contact is displayed'
contacts.find { it.email == 'rob@example.com' }?.name == 'Rob Winch'
}
def 'delete contact'() {
when: 'delete a contact'
contacts.find { it.email == 'rob@example.com' }.delete()
then: 'Delete confirmation displayed'
at DeleteConfirmPage
when: 'View Manage Page'
manage.click()
then: 'New contact has been removed'
!contacts.find { it.email == 'rob@example.com' }
}
def 'authenticated user logs out'() {
when: 'user logs out'
logout.click()
then: 'the default logout success page is displayed'
at HomePage
when: 'Unauthenticated user accesses the Manage Page'
via ContactsPage
then: 'The login page is displayed'
at LoginPage
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.pages
import geb.Page
/**
* The login page.
*
* @author Rob Winch
*/
class AddPage extends Page {
static url = 'add'
static at = { assert driver.title == 'Add New Contact'; true}
static content = {
addContact(required:false) { name = 'Rob Winch', email = 'rob@example.com'->
addForm.name = name
addForm.email = email
submit.click()
}
addForm { $('form') }
submit { $('input', type: 'submit') }
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.pages
import geb.*
/**
* The home page
*
* @author Rob Winch
*/
class ContactsPage extends Page {
static url = 'secure/'
static at = { assert driver.title == 'Your Contacts'; true}
static content = {
addContact(to: AddPage) { $('a', text: 'Add') }
contacts { moduleList Contact, $("table tr").tail() }
logout { $("input[type=submit]", value: "Logoff") }
}
}
class Contact extends Module {
static content = {
cell { $("td", it) }
id { cell(0).text().toInteger() }
name { cell(1).text() }
email { cell(2).text() }
delete { cell(3).$('a').click() }
adminPermission { cell(4).$('a') }
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.pages
import geb.Page
/**
* The home page
*
* @author Rob Winch
*/
class DeleteConfirmPage extends Page {
static at = { assert driver.title == 'Deletion completed'; true}
static content = {
manage(to: ContactsPage) { $('a', text: 'Manage') }
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.pages;
import geb.*
/**
* The home page
*
* @author Rob Winch
*/
class HomePage extends Page {
static url = ''
static at = { assert driver.title == 'Contacts Security Demo'; true}
static content = {
manage(to: [ContactsPage,LoginPage]) { $('a', text: 'Manage') }
debug { $('a', text: 'Debug').click() }
frames { $('a', text: 'Frames').click() }
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.pages;
import geb.*
/**
* The login page.
*
* @author Rob Winch
*/
class LoginPage extends Page {
static url = 'login'
static at = { assert driver.title == 'Login'; true}
static content = {
login(required:false) { user='rod', password='koala' ->
loginForm.username = user
loginForm.password = password
submit.click()
}
loginForm { $('form') }
submit { $('input', type: 'submit') }
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import org.springframework.beans.factory.annotation.Autowired;
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(form.getName(), 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);
}
}
@@ -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 sample.contact;
import org.springframework.security.acls.domain.BasePermission;
/**
* 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;
}
}
@@ -0,0 +1,64 @@
/*
* 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 sample.contact;
import org.springframework.security.acls.domain.BasePermission;
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). *");
}
}
}
}
@@ -0,0 +1,188 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.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.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 <tt>Permission</tt> 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<String, Object> model = new HashMap<String, Object>();
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<String, Object> model = new HashMap<String, Object>();
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<String, Object> model = new HashMap<String, Object>();
model.put("contact", contact);
model.put("sid", sidObject);
model.put("permission", permission);
return new ModelAndView("deletePermission", "model", model);
}
private Map<Integer, String> listPermissions() {
Map<Integer, String> map = new LinkedHashMap<Integer, String>();
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<String, String> listRecipients() {
Map<String, String> map = new LinkedHashMap<String, String>();
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);
}
}
@@ -0,0 +1,162 @@
/*
* 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 sample.contact;
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.util.StopWatch;
/**
* Demonstrates accessing the {@link ContactManager} via remoting protocols.
* <p>
* 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<String, ContactManager> 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<Contact> 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);
}
}
}
@@ -0,0 +1,96 @@
/*
* 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 sample.contact;
import java.io.Serializable;
/**
* Represents a contact.
*
* @author Ben Alex
*/
public class Contact implements Serializable {
// ~ Instance fields
// ================================================================================================
private Long id;
private String email;
private String name;
// ~ Constructors
// ===================================================================================================
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
public Contact() {
}
// ~ Methods
// ========================================================================================================
/**
* @return Returns the email.
*/
public String getEmail() {
return email;
}
/**
* @return Returns the id.
*/
public Long getId() {
return id;
}
/**
* @return Returns the name.
*/
public String getName() {
return name;
}
/**
* @param email The email to set.
*/
public void setEmail(String email) {
this.email = email;
}
public void setId(Long id) {
this.id = id;
}
/**
* @param name The name to set.
*/
public void setName(String name) {
this.name = name;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString() + ": ");
sb.append("Id: " + this.getId() + "; ");
sb.append("Name: " + this.getName() + "; ");
sb.append("Email: " + this.getEmail());
return sb.toString();
}
}
@@ -0,0 +1,43 @@
/*
* 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 sample.contact;
import java.util.List;
/**
* Provides access to the application's persistence layer.
*
* @author Ben Alex
*/
public interface ContactDao {
// ~ Methods
// ========================================================================================================
public void create(Contact contact);
public void delete(Long contactId);
public List<Contact> findAll();
public List<String> findAllPrincipals();
public List<String> findAllRoles();
public Contact getById(Long id);
public void update(Contact contact);
}
@@ -0,0 +1,117 @@
/*
* 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 sample.contact;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* Base implementation of {@link ContactDao} that uses Spring's JdbcTemplate.
*
* @author Ben Alex
* @author Luke Taylor
*/
public class ContactDaoSpring extends JdbcDaoSupport implements ContactDao {
// ~ Methods
// ========================================================================================================
public void create(final Contact contact) {
getJdbcTemplate().update("insert into contacts values (?, ?, ?)",
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setLong(1, contact.getId());
ps.setString(2, contact.getName());
ps.setString(3, contact.getEmail());
}
});
}
public void delete(final Long contactId) {
getJdbcTemplate().update("delete from contacts where id = ?",
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setLong(1, contactId);
}
});
}
public void update(final Contact contact) {
getJdbcTemplate().update(
"update contacts set contact_name = ?, address = ? where id = ?",
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, contact.getName());
ps.setString(2, contact.getEmail());
ps.setLong(3, contact.getId());
}
});
}
public List<Contact> findAll() {
return getJdbcTemplate().query(
"select id, contact_name, email from contacts order by id",
new RowMapper<Contact>() {
public Contact mapRow(ResultSet rs, int rowNum) throws SQLException {
return mapContact(rs);
}
});
}
public List<String> findAllPrincipals() {
return getJdbcTemplate().queryForList(
"select username from users order by username", String.class);
}
public List<String> findAllRoles() {
return getJdbcTemplate().queryForList(
"select distinct authority from authorities order by authority",
String.class);
}
public Contact getById(Long id) {
List<Contact> list = getJdbcTemplate().query(
"select id, contact_name, email from contacts where id = ? order by id",
new RowMapper<Contact>() {
public Contact mapRow(ResultSet rs, int rowNum) throws SQLException {
return mapContact(rs);
}
}, id);
if (list.size() == 0) {
return null;
}
else {
return (Contact) list.get(0);
}
}
private Contact mapContact(ResultSet rs) throws SQLException {
Contact contact = new Contact();
contact.setId(new Long(rs.getLong("id")));
contact.setName(rs.getString("contact_name"));
contact.setEmail(rs.getString("email"));
return contact;
}
}
@@ -0,0 +1,57 @@
/*
* 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 sample.contact;
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 java.util.List;
/**
* 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<Contact> getAll();
@PreAuthorize("hasRole('ROLE_USER')")
public List<String> getAllRecipients();
@PreAuthorize("hasPermission(#id, 'sample.contact.Contact', read) or "
+ "hasPermission(#id, 'sample.contact.Contact', admin)")
public Contact getById(Long id);
public Contact getRandomContact();
}
@@ -0,0 +1,196 @@
/*
* 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 sample.contact;
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.transaction.annotation.Transactional;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.util.Assert;
import java.util.List;
import java.util.Random;
/**
* Concrete implementation of {@link ContactManager}.
*
* @author Ben Alex
*/
@Transactional
public class ContactManagerBackend extends ApplicationObjectSupport implements
ContactManager, InitializingBean {
// ~ Instance fields
// ================================================================================================
private ContactDao contactDao;
private MutableAclService mutableAclService;
private int counter = 1000;
// ~ Methods
// ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(contactDao, "contactDao required");
Assert.notNull(mutableAclService, "mutableAclService required");
}
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<AccessControlEntry> 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<Contact> getAll() {
logger.debug("Returning all contacts");
return contactDao.findAll();
}
@Transactional(readOnly = true)
public List<String> 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<Contact> 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);
}
}
@@ -0,0 +1,283 @@
/*
* 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 sample.contact;
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.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(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<Object>() {
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<Object>() {
public Object doInTransaction(TransactionStatus arg0) {
mutableAclService.updateAcl(acl);
return null;
}
});
}
}
@@ -0,0 +1,108 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.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.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.
* <p>
* This controller displays a list of all the contacts for which the current user has
* read or admin permissions. It makes a call to {@link ContactManager#getAll()} which
* automatically filters the returned list using Spring Security's ACL mechanism (see
* the expression annotations on this interface for the details).
* <p>
* In addition to rendering the list of contacts, the view will also include a "Del"
* or "Admin" link beside the contact, depending on whether the user has the
* corresponding permissions (admin permission is assumed to imply delete here). This
* information is stored in the model using the injected {@link PermissionEvaluator}
* instance. The implementation should be an instance of
* {@link AclPermissionEvaluator} or one which is compatible with Spring Security's
* ACL module.
*/
@RequestMapping(value = "/secure/index.htm", method = RequestMethod.GET)
public ModelAndView displayUserContacts() {
List<Contact> myContactsList = contactManager.getAll();
Map<Contact, Boolean> hasDelete = new HashMap<Contact, Boolean>(
myContactsList.size());
Map<Contact, Boolean> hasAdmin = new HashMap<Contact, Boolean>(
myContactsList.size());
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<String, Object> model = new HashMap<String, Object>();
model.put("contacts", myContactsList);
model.put("hasDeletePermission", hasDelete);
model.put("hasAdminPermission", hasAdmin);
return new ModelAndView("index", "model", model);
}
}
@@ -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 sample.contact;
/**
* 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;
}
}
@@ -0,0 +1,50 @@
/*
* 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 sample.contact;
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. *");
}
}
}
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">
<!--
- Application context containing the ACL beans.
-
-->
<!-- ========= ACL SERVICE DEFINITIONS ========= -->
<bean id="aclCache" class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
</property>
<property name="cacheName" value="aclCache"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ACL_ADMIN"/>
</bean>
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
</constructor-arg>
</bean>
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="lookupStrategy"/>
<constructor-arg ref="aclCache"/>
</bean>
</beans>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context containing business beans.
-
- Used by all artifacts.
-
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/springframework/security/messages"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:test"/>
<!-- <value>jdbc:hsqldb:hsql://localhost/acl</value> -->
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
<property name="dataSource" ref="dataSource"/>
<property name="mutableAclService" ref="aclService"/>
<property name="platformTransactionManager" ref="transactionManager"/>
</bean>
<bean id="contactManager" class="sample.contact.ContactManagerBackend">
<property name="contactDao">
<bean class="sample.contact.ContactDaoSpring">
<property name="dataSource" ref="dataSource"/>
</bean>
</property>
<property name="mutableAclService" ref="aclService"/>
</bean>
</beans>
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context containing authentication, channel
- security and web URI beans.
-
- Only used by "filter" artifact.
-
-->
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<global-method-security pre-post-annotations="enabled">
<expression-handler ref="expressionHandler"/>
</global-method-security>
<http realm="Contacts Realm" use-expressions="false">
<intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/index.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/hello.htm" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/switchuser.jsp" access="ROLE_SUPERVISOR"/>
<intercept-url pattern="/login/impersonate" access="ROLE_SUPERVISOR"/>
<intercept-url pattern="/**" access="ROLE_USER"/>
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1"/>
<http-basic/>
<logout logout-success-url="/index.jsp"/>
<remember-me />
<headers/>
<csrf/>
<custom-filter ref="switchUserProcessingFilter" position="SWITCH_USER_FILTER"/>
</http>
<b:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<authentication-manager>
<authentication-provider>
<password-encoder ref="encoder"/>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
</authentication-manager>
<!-- Automatically receives AuthenticationEvent messages -->
<b:bean id="loggerListener" class="org.springframework.security.authentication.event.LoggerListener"/>
<!-- Filter used to switch the user context. Note: the switch and exit url must be secured
based on the role granted the ability to 'switch' to another user -->
<!-- In this example 'rod' has ROLE_SUPERVISOR that can switch to regular ROLE_USER(s) -->
<b:bean id="switchUserProcessingFilter" class="org.springframework.security.web.authentication.switchuser.SwitchUserFilter" autowire="byType">
<b:property name="targetUrl" value="/secure/index.htm"/>
</b:bean>
<b:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<b:property name="permissionEvaluator" ref="permissionEvaluator"/>
<b:property name="permissionCacheOptimizer">
<b:bean class="org.springframework.security.acls.AclPermissionCacheOptimizer">
<b:constructor-arg ref="aclService"/>
</b:bean>
</b:property>
</b:bean>
<b:bean id="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator">
<b:constructor-arg ref="aclService"/>
</b:bean>
</b:beans>
@@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- <logger name="org.springframework.security" level="DEBUG"/> -->
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -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).
@@ -0,0 +1,27 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- ========================== WEB DEFINITIONS ======================= -->
<context:component-scan base-package="sample.contact"/>
<context:annotation-config />
<mvc:annotation-driven/>
<mvc:view-controller path="/frames.htm" view-name="/frames"/>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
@@ -0,0 +1,42 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Add New Contact</title></head>
<body>
<h1>Add Contact</h1>
<form method="post">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td alignment="right" width="20%">Name:</td>
<spring:bind path="webContact.name">
<td width="20%">
<input type="text" name="name" value="<c:out value="${status.value}"/>">
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
<tr>
<td alignment="right" width="20%">Email:</td>
<spring:bind path="webContact.email">
<td width="20%">
<input type="text" name="email" value="<c:out value="${status.value}"/>">
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
</table>
<br>
<spring:hasBindErrors name="webContact">
<b>Please fix all errors!</b>
</spring:hasBindErrors>
<br><br>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
<input name="execute" type="submit" alignment="center" value="Execute">
</form>
<a href="<c:url value="../hello.htm"/>">Home</a>
</body>
</html>
@@ -0,0 +1,56 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Add Permission</title></head>
<body>
<h1>Add Permission</h1>
<form method="post">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td alignment="right" width="20%">Contact:</td>
<td width="60%"><c:out value="${addPermission.contact}"/></td>
</tr>
<tr>
<td alignment="right" width="20%">Recipient:</td>
<spring:bind path="addPermission.recipient">
<td width="20%">
<select name="<c:out value="${status.expression}"/>">
<c:forEach var="thisRecipient" items="${recipients}">
<option <c:if test="${thisRecipient.key == status.value}">selected</c:if> value="<c:out value="${thisRecipient.key}"/>">
<c:out value="${thisRecipient.value}"/></option>
</c:forEach>
</select>
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
<tr>
<td alignment="right" width="20%">Permission:</td>
<spring:bind path="addPermission.permission">
<td width="20%">
<select name="<c:out value="${status.expression}"/>">
<c:forEach var="thisPermission" items="${permissions}">
<option <c:if test="${thisPermission.key == status.value}">selected</c:if> value="<c:out value="${thisPermission.key}"/>">
<c:out value="${thisPermission.value}"/></option>
</c:forEach>
</select>
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
</table>
<br>
<spring:hasBindErrors name="webContact">
<b>Please fix all errors!</b>
</spring:hasBindErrors>
<br><br>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
<input name="execute" type="submit" alignment="center" value="Execute">
</form>
<p>
<A HREF="<c:url value="adminPermission.htm"><c:param name="contactId" value="${addPermission.contact.id}"/></c:url>">Admin Permission</A> <a href="<c:url value="index.htm"/>">Manage</a>
</body>
</html>
@@ -0,0 +1,30 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Administer Permissions</title></head>
<body>
<h1>Administer Permissions</h1>
<p>
<code>
<c:out value="${model.contact}"/>
</code>
</p>
<table cellpadding="3" border="0">
<c:forEach var="acl" items="${model.acl.entries}">
<tr>
<td>
<code>
<c:out value="${acl}"/>
</code>
</td>
<td>
<a href="<c:url value="deletePermission.htm"><c:param name="contactId" value="${model.contact.id}"/><c:param name="sid" value="${acl.sid.principal}"/><c:param name="permission" value="${acl.permission.mask}"/></c:url>">Del</a>
</td>
</tr>
</c:forEach>
</table>
<p>
<a href="<c:url value="addPermission.htm"><c:param name="contactId" value="${model.contact.id}"/></c:url>">Add Permission</a> <a href="<c:url value="index.htm"/>">Manage</a>
</p>
</body>
</html>
@@ -0,0 +1,20 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Permission Deleted</title></head>
<body>
<h1>Permission Deleted</h1>
<P>
<code>
<c:out value="${model.contact}"/>
</code>
<P>
<code>
<c:out value="${model.sid}"/>
</code>
<code>
<c:out value="${model.permission}"/>
</code>
<p><a href="<c:url value="index.htm"/>">Manage</a>
</body>
</html>
@@ -0,0 +1,13 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Deletion completed</title></head>
<body>
<h1>Deleted</h1>
<P>
<code>
<c:out value="${contact}"/>
</code>
<p><a href="<c:url value="index.htm"/>">Manage</a>
</body>
</html>
@@ -0,0 +1,10 @@
<html>
<head>
<title>Frames</title>
</head>
<body>
<p>This contains frames, but the frames will not be loaded due to the <a href="http://tools.ietf.org/html/draft-ietf-websec-x-frame-options">X-Frame-Options</a>
being specified as denied. This protects against <a href="http://en.wikipedia.org/wiki/Clickjacking">clickjacking attacks</a></p>
<iframe src="./hello.htm" width="500" height="500"></iframe>
</body>
</html>
@@ -0,0 +1,52 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Contacts Security Demo</title></head>
<body>
<h1>Contacts Security Demo</h1>
<P>Contacts demonstrates the following central Spring Security capabilities:
<ul>
<li><b>Role-based security</b>. Each principal is a member of certain roles,
which are used to restrict access to certain secure objects.</li>
<li><b>Domain object instance security</b>. The <code>Contact</code>, the
main domain object in the application, has an access control list (ACL)
that indicates who is allowed read, administer and delete the object.</li>
<li><b>Method invocation security</b>. The <code>ContactManager</code> service
layer bean has a number of secured (protected) and public (unprotected)
methods.</li>
<li><b>Web request security</b>. The <code>/secure</code> URI path is protected
by Spring Security from principals not holding the
<code>ROLE_USER</code> granted authority.</li>
<li><b>Security unaware application objects</b>. None of the objects
are aware of the security being implemented by Spring Security. *</li>
<li><b>Security taglib usage</b>. All of the JSPs use Spring Security's
taglib to evaluate security information. *</li>
<li><b>Fully declarative security</b>. Every capability is configured in
the application context using standard Spring Security classes. *</li>
<li><b>Database-sourced security data</b>. All of the user, role and ACL
information is obtained from an in-memory JDBC-compliant database.</li>
<li><b>Integrated form-based and BASIC authentication</b>. Any BASIC
authentication header is detected and used for authentication. Normal
interactive form-based authentication is used by default.</li>
<li><b>Remember-me services</b>. Spring Security's pluggable remember-me
strategy is demonstrated, with a corresponding checkbox on the login form.</li>
</ul>
* As the application provides an "ACL Administration" use case, those
classes are necessarily aware of security. But no business use cases are.
<p>Please excuse the lack of look 'n' feel polish in this application.
It is about security, after all! :-)
<p>To demonstrate a public method on <code>ContactManager</code>,
here's a random <code>Contact</code>:
<p>
<code>
<c:out value="${contact}"/>
</code>
<p>Get started by clicking "Manage"...
<p><A HREF="<c:url value="secure/index.htm"/>">Manage</a>
<a href="<c:url value="secure/debug.jsp"/>">Debug</a>
<a href="<c:url value="./frames.htm"/>">Frames</a>
</body>
</html>
@@ -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" %>
@@ -0,0 +1,37 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Your Contacts</title></head>
<body>
<h1><security:authentication property="principal.username"/>'s Contacts</h1>
<P>
<table cellpadding=3 border=0>
<tr><td><b>id</b></td><td><b>Name</b></td><td><b>Email</b></td></tr>
<c:forEach var="contact" items="${model.contacts}" >
<tr>
<td>
<c:out value="${contact.id}"/>
</td>
<td>
<c:out value="${contact.name}"/>
</td>
<td>
<c:out value="${contact.email}"/>
</td>
<c:if test="${model.hasDeletePermission[contact]}">
<td><a href="<c:url value="del.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Del</a></td>
</c:if>
<c:if test="${model.hasAdminPermission[contact]}">
<td><a href="<c:url value="adminPermission.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Admin Permission</a></td>
</c:if>
</tr>
</c:forEach>
</table>
<p><a href="<c:url value="add.htm"/>">Add</a> </p>
<form action="<c:url value="/logout"/>" method="post">
<input type="submit" value="Logoff"/> (also clears any remember-me cookie)
<security:csrfInput/>
</form>
</body>
</html>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!--
- Contacts web application
-->
<beans>
<!-- RMI exporter for the ContactManager -->
<!-- This could just as easily have been in
applicationContext-common-business.xml, because it doesn't rely on
DispatcherServlet or indeed any other HTTP services. It's in this
application context simply for logical placement with other
remoting exporters. -->
<!-- COMMENTED OUT BY DEFAULT TO AVOID CONFLICTS WITH APPLICATION SERVERS
<bean id="contactManager-rmi" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="service"><ref bean="contactManager"/></property>
<property name="serviceInterface">
<value>sample.contact.ContactManager</value>
</property>
<property name="serviceName"><value>contactManager</value></property>
<property name="registryPort"><value>1099</value></property>
</bean>
-->
<!-- HTTP invoker exporter for the ContactManager -->
<!-- Spring's HTTP invoker uses Java serialization via HTTP -->
<bean name="/ContactManager-httpinvoker" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="contactManager"/>
<property name="serviceInterface" value="sample.contact.ContactManager"/>
</bean>
<!-- Hessian exporter for the ContactManager -->
<!-- Hessian is a slim binary HTTP remoting protocol -->
<!--
<bean name="/ContactManager-hessian" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="contactManager"/>
<property name="serviceInterface" value="sample.contact.ContactManager"/>
</bean>
-->
<!-- Burlap exporter for the ContactManager -->
<!-- Burlap is a slim XML-based HTTP remoting protocol -->
<!--
<bean name="/ContactManager-burlap" class="org.springframework.remoting.caucho.BurlapServiceExporter">
<property name="service" ref="contactManager"/>
<property name="serviceInterface" value="sample.contact.ContactManager"/>
</bean>
-->
</beans>
@@ -0,0 +1,311 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.1.1</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>Spring</short-name>
<uri>http://www.springframework.org/tags</uri>
<description>Spring Framework JSP Tag Library. Authors: Rod Johnson, Juergen Hoeller</description>
<tag>
<name>htmlEscape</name>
<tag-class>org.springframework.web.servlet.tags.HtmlEscapeTag</tag-class>
<body-content>JSP</body-content>
<description>
Sets default HTML escape value for the current page.
Overrides a "defaultHtmlEscape" context-param in web.xml, if any.
</description>
<attribute>
<name>defaultHtmlEscape</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>escapeBody</name>
<tag-class>org.springframework.web.servlet.tags.EscapeBodyTag</tag-class>
<body-content>JSP</body-content>
<description>
Escapes its enclosed body content, applying HTML escaping and/or JavaScript escaping.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>javaScriptEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>message</name>
<tag-class>org.springframework.web.servlet.tags.MessageTag</tag-class>
<body-content>JSP</body-content>
<description>
Retrieves the message with the given code, or text if code isn't resolvable.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>code</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>arguments</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>text</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>javaScriptEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>theme</name>
<tag-class>org.springframework.web.servlet.tags.ThemeTag</tag-class>
<body-content>JSP</body-content>
<description>
Retrieves the theme message with the given code, or text if code isn't resolvable.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>code</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>arguments</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>text</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>javaScriptEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>hasBindErrors</name>
<tag-class>org.springframework.web.servlet.tags.BindErrorsTag</tag-class>
<body-content>JSP</body-content>
<description>
Provides Errors instance in case of bind errors.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<variable>
<name-given>errors</name-given>
<variable-class>org.springframework.validation.Errors</variable-class>
</variable>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>nestedPath</name>
<tag-class>org.springframework.web.servlet.tags.NestedPathTag</tag-class>
<body-content>JSP</body-content>
<description>
Sets a nested path to be used by the bind tag's path.
</description>
<variable>
<name-given>nestedPath</name-given>
<variable-class>java.lang.String</variable-class>
</variable>
<attribute>
<name>path</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>bind</name>
<tag-class>org.springframework.web.servlet.tags.BindTag</tag-class>
<body-content>JSP</body-content>
<description>
Provides BindStatus object for the given bind path.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<variable>
<name-given>status</name-given>
<variable-class>org.springframework.web.servlet.support.BindStatus</variable-class>
</variable>
<attribute>
<name>path</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>ignoreNestedPath</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>transform</name>
<tag-class>org.springframework.web.servlet.tags.TransformTag</tag-class>
<body-content>JSP</body-content>
<description>
Provides transformation of variables to Strings, using an appropriate
custom PropertyEditor from BindTag (can only be used inside BindTag).
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Contacts web application
-
-->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Contacts Sample Application</display-name>
<!--
- Location of the XML file that defines the root application context
- Applied by ContextLoaderListener.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext-common-business.xml
classpath:applicationContext-common-authorization.xml
classpath:applicationContext-security.xml
</param-value>
</context-param>
<!-- Nothing below here needs to be modified -->
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>contacts.root</param-value>
</context-param>
<filter>
<filter-name>localizationFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>localizationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
- Loads the root application context of this web app at startup.
- The application context is then available via
- WebApplicationContextUtils.getWebApplicationContext(servletContext).
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
- Provides core MVC application controller. See contacts-servlet.xml.
-->
<servlet>
<servlet-name>contacts</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!--
- Provides web services endpoint. See remoting-servlet.xml.
-->
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>contacts</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
</web-app>
@@ -0,0 +1,22 @@
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %>
<%@ page import="org.springframework.security.core.Authentication" %>
<html>
<head>
<title>Access Denied</title>
</head>
<body>
<h1>Sorry, access is denied</h1>
<p>
<%= request.getAttribute("SPRING_SECURITY_403_EXCEPTION")%>
</p>
<p>
<% Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
Authentication object as a String: <%= auth.toString() %><br /><br />
<% } %>
</p>
</body>
</html>
@@ -0,0 +1,5 @@
<html>
<title>Access denied!</title>
<h1>Access Denied</h1>
<p>We're sorry, but you are not authorized to perform the requested operation.</p>
</html>
@@ -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" %>
<html>
<head>
<title>Exit User</title>
</head>
<body>
<h1>Exit User</h1>
<c:if test="${not empty param.login_error}">
<font color="red">
Your 'Exit User' attempt was not successful, try again.<br/><br/>
Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>
</font>
</c:if>
<form action="<c:url value='logout/impersonate'/>" method="POST">
<table>
<tr><td>Current User:</td><td>
<%
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
<%= auth.getPrincipal().toString() %>
<% } %>
</td></tr>
<tr><td colspan='2'><input name="exit" type="submit" value="Exit"></td></tr>
</table>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
</form>
</body>
</html>
@@ -0,0 +1,4 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%-- Redirected because we can't set the welcome page to a virtual URL. --%>
<c:redirect url="/hello.htm"/>
@@ -0,0 +1,47 @@
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page pageEncoding="UTF-8" %>
<html>
<head>
<title>Login</title>
</head>
<body onload="document.f.username.focus();">
<h1>Login</h1>
<p>Valid users:
<p>
<p>username <b>rod</b>, password <b>koala</b>
<p>username <b>dianne</b>, password <b>emu</b>
<p>username <b>scott</b>, password <b>wombat</b>
<p>username <b>peter</b>, password <b>opal</b> (user disabled)
<p>username <b>bill</b>, password <b>wombat</b>
<p>username <b>bob</b>, password <b>wombat</b>
<p>username <b>jane</b>, password <b>wombat</b>
<p>
<p>Locale is: <%= request.getLocale() %></p>
<%-- this form-login-page form is also used as the
form-error-page to ask for a login again.
--%>
<c:if test="${not empty param.login_error}">
<font color="red">
Your login attempt was not successful, try again.<br/><br/>
Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.
</font>
</c:if>
<form name="f" action="<c:url value='login'/>" method="POST">
<table>
<tr><td>User:</td><td><input type='text' name='username' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'></td></tr>
<tr><td><input type="checkbox" name="remember-me"></td><td>Don't ask for my password for two weeks</td></tr>
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
</table>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
</form>
</body>
</html>
@@ -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" %>
<html>
<head>
<title>Security Debug Information</title>
</head>
<body>
<h3>Security Debug Information</h3>
<%
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
<p>
Authentication object is of type: <em><%= auth.getClass().getName() %></em>
</p>
<p>
Authentication object as a String: <br/><br/><%= auth.toString() %>
</p>
Authentication object holds the following granted authorities:<br /><br />
<%
for (GrantedAuthority authority : auth.getAuthorities()) { %>
<%= authority %> (<em>getAuthority()</em>: <%= authority.getAuthority() %>)<br />
<% }
%>
<p><b>Success! Your web filters appear to be properly configured!</b></p>
<%
} else {
%>
Authentication object is null.<br />
This is an error and your Spring Security application will not operate properly until corrected.<br /><br />
<% }
%>
</body>
</html>
@@ -0,0 +1,42 @@
<%@ 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" %>
<html>
<head>
<title>Switch User</title>
</head>
<body>
<h1>Switch to User</h1>
<h3>Valid users:</h3>
<p>username <b>rod</b>, password <b>koala</b></p>
<p>username <b>dianne</b>, password <b>emu</b></p>
<p>username <b>scott</b>, password <b>wombat</b></p>
<p>username <b>bill</b>, password <b>wombat</b></p>
<p>username <b>bob</b>, password <b>wombat</b></p>
<p>username <b>jane</b>, password <b>wombat</b></p>
<%-- this form-login-page form is also used as the
form-error-page to ask for a login again.
--%>
<c:if test="${not empty param.login_error}">
<p>
<font color="red">
Your 'su' attempt was not successful, try again.<br/>
</font>
</p>
</c:if>
<form action="<c:url value='login/impersonate'/>" method="POST">
<table>
<tr><td>User:</td><td><input type='text' name='username'></td></tr>
<tr><td colspan='2'><input name="switch" type="submit" value="Switch to User"></td></tr>
</table>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
</form>
</body>
</html>
@@ -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/login/cas?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
@@ -0,0 +1,175 @@
/*
* 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 sample.contact;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Tests {@link ContactManager}.
*
* @author David Leal
* @author Ben Alex
* @author Luke Taylor
*/
@ContextConfiguration(locations = { "/applicationContext-security.xml",
"/applicationContext-common-authorization.xml",
"/applicationContext-common-business.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class ContactManagerTests {
// ~ Instance fields
// ================================================================================================
@Autowired
protected ContactManager contactManager;
// ~ Methods
// ========================================================================================================
void assertContainsContact(long id, List<Contact> contacts) {
for (Contact contact : contacts) {
if (contact.getId().equals(Long.valueOf(id))) {
return;
}
}
fail("List of contacts should have contained: " + id);
}
void assertDoestNotContainContact(long id, List<Contact> contacts) {
for (Contact contact : contacts) {
if (contact.getId().equals(Long.valueOf(id))) {
fail("List of contact should NOT (but did) contain: " + id);
}
}
}
/**
* Locates the first <code>Contact</code> of the exact name specified.
* <p>
* Uses the {@link ContactManager#getAll()} method.
*
* @param id Identify of the contact to locate (must be an exact match)
*
* @return the domain or <code>null</code> if not found
*/
Contact getContact(String id) {
for (Contact contact : contactManager.getAll()) {
if (contact.getId().equals(id)) {
return contact;
}
}
return null;
}
private void makeActiveUser(String username) {
String password = "";
if ("rod".equals(username)) {
password = "koala";
}
else if ("dianne".equals(username)) {
password = "emu";
}
else if ("scott".equals(username)) {
password = "wombat";
}
else if ("peter".equals(username)) {
password = "opal";
}
Authentication authRequest = new UsernamePasswordAuthenticationToken(username,
password);
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Test
public void testDianne() {
makeActiveUser("dianne"); // has ROLE_USER
List<Contact> contacts = contactManager.getAll();
assertThat(contacts).hasSize(4);
assertContainsContact(4, contacts);
assertContainsContact(5, contacts);
assertContainsContact(6, contacts);
assertContainsContact(8, contacts);
assertDoestNotContainContact(1, contacts);
assertDoestNotContainContact(2, contacts);
assertDoestNotContainContact(3, contacts);
}
@Test
public void testrod() {
makeActiveUser("rod"); // has ROLE_SUPERVISOR
List<Contact> contacts = contactManager.getAll();
assertThat(contacts).hasSize(4);
assertContainsContact(1, contacts);
assertContainsContact(2, contacts);
assertContainsContact(3, contacts);
assertContainsContact(4, contacts);
assertDoestNotContainContact(5, contacts);
Contact c1 = contactManager.getById(new Long(4));
contactManager.deletePermission(c1, new PrincipalSid("bob"),
BasePermission.ADMINISTRATION);
contactManager.addPermission(c1, new PrincipalSid("bob"),
BasePermission.ADMINISTRATION);
}
@Test
public void testScott() {
makeActiveUser("scott"); // has ROLE_USER
List<Contact> contacts = contactManager.getAll();
assertThat(contacts).hasSize(5);
assertContainsContact(4, contacts);
assertContainsContact(6, contacts);
assertContainsContact(7, contacts);
assertContainsContact(8, contacts);
assertContainsContact(9, contacts);
assertDoestNotContainContact(1, contacts);
}
}
@@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.security" level="${sec.log.level}:-WARN"/>
<root level="${root.level}:-WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
+16
View File
@@ -0,0 +1,16 @@
dependencies {
compile project(':spring-security-core'),
project(':spring-security-acl'),
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-tx:$springVersion",
"org.springframework:spring-jdbc:$springVersion"
testCompile "org.springframework:spring-context:$springVersion"
runtime project(':spring-security-config'),
"org.hsqldb:hsqldb:$hsqlVersion",
"org.springframework:spring-context-support:$springVersion"
optional "net.sf.ehcache:ehcache:$ehcacheVersion"
}
+161
View File
@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-samples-xml-dms</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<name>spring-security-samples-xml-dms</name>
<description>spring-security-samples-xml-dms</description>
<url>http://spring.io/spring-security</url>
<organization>
<name>spring.io</name>
<url>http://spring.io/</url>
</organization>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rwinch</id>
<name>Rob Winch</name>
<email>rwinch@gopivotal.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-projects/spring-security</connection>
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
<url>https://github.com/spring-projects/spring-security</url>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.9.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,101 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.util.Assert;
/**
* @author Ben Alex
*
*/
public abstract class AbstractElement {
/** The name of this token (a filename or directory segment name */
private String name;
/** The parent of this token (a directory, or null if referring to root) */
private AbstractElement parent;
/** The database identifier for this object (null if not persisted) */
private Long id;
/**
* Constructor to use to represent a root element. A root element has an id of -1.
*/
protected AbstractElement() {
this.name = "/";
this.parent = null;
this.id = new Long(-1);
}
/**
* Constructor to use to represent a non-root element.
*
* @param name name for this element (required, cannot be "/")
* @param parent for this element (required, cannot be null)
*/
protected AbstractElement(String name, AbstractElement parent) {
Assert.hasText(name, "Name required");
Assert.notNull(parent, "Parent required");
Assert.notNull(parent.getId(),
"The parent must have been saved in order to create a child");
this.name = name;
this.parent = parent;
}
public Long getId() {
return id;
}
/**
* @return the name of this token (never null, although will be "/" if root, otherwise
* it won't include separators)
*/
public String getName() {
return name;
}
public AbstractElement getParent() {
return parent;
}
/**
* @return the fully-qualified name of this element, including any parents
*/
public String getFullName() {
List<String> strings = new ArrayList<String>();
AbstractElement currentElement = this;
while (currentElement != null) {
strings.add(0, currentElement.getName());
currentElement = currentElement.getParent();
}
StringBuilder sb = new StringBuilder();
String lastCharacter = null;
for (Iterator<String> i = strings.iterator(); i.hasNext();) {
String token = i.next();
if (!"/".equals(lastCharacter) && lastCharacter != null) {
sb.append("/");
}
sb.append(token);
lastCharacter = token.substring(token.length() - 1);
}
return sb.toString();
}
}
@@ -0,0 +1,165 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
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.util.Assert;
/**
* Populates the DMS in-memory database with document and ACL information.
*
* @author Ben Alex
*/
public class DataSourcePopulator implements InitializingBean {
protected static final int LEVEL_NEGATE_READ = 0;
protected static final int LEVEL_GRANT_READ = 1;
protected static final int LEVEL_GRANT_WRITE = 2;
protected static final int LEVEL_GRANT_ADMIN = 3;
protected JdbcTemplate template;
protected DocumentDao documentDao;
public DataSourcePopulator(DataSource dataSource, DocumentDao documentDao) {
Assert.notNull(dataSource, "DataSource required");
Assert.notNull(documentDao, "DocumentDao required");
this.template = new JdbcTemplate(dataSource);
this.documentDao = documentDao;
}
public void afterPropertiesSet() throws Exception {
// ACL tables
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));");
// Normal authentication tables
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);");
// Document management system business tables
template.execute("CREATE TABLE DIRECTORY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY, DIRECTORY_NAME VARCHAR_IGNORECASE(50) NOT NULL, PARENT_DIRECTORY_ID BIGINT)");
template.execute("CREATE TABLE FILE(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY, FILE_NAME VARCHAR_IGNORECASE(50) NOT NULL, CONTENT VARCHAR_IGNORECASE(1024), PARENT_DIRECTORY_ID BIGINT)");
// Populate the authentication and role tables
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');");
// Now create an ACL entry for the root directory
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("rod", "ignored", AuthorityUtils
.createAuthorityList(("ROLE_IGNORED"))));
addPermission(documentDao, Directory.ROOT_DIRECTORY, "ROLE_USER",
LEVEL_GRANT_WRITE);
// Now go off and create some directories and files for our users
createSampleData("rod", "koala");
createSampleData("dianne", "emu");
createSampleData("scott", "wombat");
}
/**
* Creates a directory for the user, and a series of sub-directories. The root
* directory is the parent for the user directory. The sub-directories are
* "confidential" and "shared". The ROLE_USER will be given read and write access to
* "shared".
*/
private void createSampleData(String username, String password) {
Assert.notNull(documentDao, "DocumentDao required");
Assert.hasText(username, "Username required");
Authentication auth = new UsernamePasswordAuthenticationToken(username, password);
try {
// Set the SecurityContextHolder ThreadLocal so any subclasses
// automatically know which user is operating
SecurityContextHolder.getContext().setAuthentication(auth);
// Create the home directory first
Directory home = new Directory(username, Directory.ROOT_DIRECTORY);
documentDao.create(home);
addPermission(documentDao, home, username, LEVEL_GRANT_ADMIN);
addPermission(documentDao, home, "ROLE_USER", LEVEL_GRANT_READ);
createFiles(documentDao, home);
// Now create the confidential directory
Directory confid = new Directory("confidential", home);
documentDao.create(confid);
addPermission(documentDao, confid, "ROLE_USER", LEVEL_NEGATE_READ);
createFiles(documentDao, confid);
// Now create the shared directory
Directory shared = new Directory("shared", home);
documentDao.create(shared);
addPermission(documentDao, shared, "ROLE_USER", LEVEL_GRANT_READ);
addPermission(documentDao, shared, "ROLE_USER", LEVEL_GRANT_WRITE);
createFiles(documentDao, shared);
}
finally {
// Clear the SecurityContextHolder ThreadLocal so future calls are
// guaranteed to be clean
SecurityContextHolder.clearContext();
}
}
private void createFiles(DocumentDao documentDao, Directory parent) {
Assert.notNull(documentDao, "DocumentDao required");
Assert.notNull(parent, "Parent required");
int countBeforeInsert = documentDao.findElements(parent).length;
for (int i = 0; i < 10; i++) {
File file = new File("file_" + i + ".txt", parent);
documentDao.create(file);
}
Assert.isTrue(countBeforeInsert + 10 == documentDao.findElements(parent).length,
"Failed to increase count by 10");
}
/**
* Allows subclass to add permissions.
*
* @param documentDao that will presumably offer methods to enable the operation to be
* completed
* @param element to the subject of the new permissions
* @param recipient to receive permission (if it starts with ROLE_ it is assumed to be
* a GrantedAuthority, else it is a username)
* @param level based on the static final integer fields on this class
*/
protected void addPermission(DocumentDao documentDao, AbstractElement element,
String recipient, int level) {
}
}
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms;
/**
*
* @author Ben Alex
*
*/
public class Directory extends AbstractElement {
public static final Directory ROOT_DIRECTORY = new Directory();
private Directory() {
super();
}
public Directory(String name, Directory parent) {
super(name, parent);
}
public String toString() {
return "Directory[fullName='" + getFullName() + "'; name='" + getName()
+ "'; id='" + getId() + "'; parent='" + getParent() + "']";
}
}
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms;
/**
*
* @author Ben Alex
*
*/
public interface DocumentDao {
/**
* Creates an entry in the database for the element.
*
* @param element an unsaved element (the "id" will be updated after method is
* invoked)
*/
public void create(AbstractElement element);
/**
* Removes a file from the database for the specified element.
*
* @param file the file to remove (cannot be null)
*/
public void delete(File file);
/**
* Modifies a file in the database.
*
* @param file the file to update (cannot be null)
*/
public void update(File file);
/**
* Locates elements in the database which appear under the presented directory
*
* @param directory the directory (cannot be null - use
* {@link Directory#ROOT_DIRECTORY} for root)
* @return zero or more elements in the directory (an empty array may be returned -
* never null)
*/
public AbstractElement[] findElements(Directory directory);
}
@@ -0,0 +1,153 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.util.FieldUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Basic JDBC implementation of {@link DocumentDao}.
*
* @author Ben Alex
*/
public class DocumentDaoImpl extends JdbcDaoSupport implements DocumentDao {
private static final String INSERT_INTO_DIRECTORY = "insert into directory(directory_name, parent_directory_id) values (?,?)";
private static final String INSERT_INTO_FILE = "insert into file(file_name, content, parent_directory_id) values (?,?,?)";
private static final String SELECT_FROM_DIRECTORY = "select id from directory where parent_directory_id = ?";
private static final String SELECT_FROM_DIRECTORY_NULL = "select id from directory where parent_directory_id is null";
private static final String SELECT_FROM_FILE = "select id, file_name, content, parent_directory_id from file where parent_directory_id = ?";
private static final String SELECT_FROM_DIRECTORY_SINGLE = "select id, directory_name, parent_directory_id from directory where id = ?";
private static final String DELETE_FROM_FILE = "delete from file where id = ?";
private static final String UPDATE_FILE = "update file set content = ? where id = ?";
private static final String SELECT_IDENTITY = "call identity()";
private Long obtainPrimaryKey() {
Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(),
"Transaction must be running");
return getJdbcTemplate().queryForObject(SELECT_IDENTITY, Long.class);
}
public void create(AbstractElement element) {
Assert.notNull(element, "Element required");
Assert.isNull(element.getId(), "Element has previously been saved");
if (element instanceof Directory) {
Directory directory = (Directory) element;
Long parentId = directory.getParent() == null ? null : directory.getParent()
.getId();
getJdbcTemplate().update(INSERT_INTO_DIRECTORY,
new Object[] { directory.getName(), parentId });
FieldUtils.setProtectedFieldValue("id", directory, obtainPrimaryKey());
}
else if (element instanceof File) {
File file = (File) element;
Long parentId = file.getParent() == null ? null : file.getParent().getId();
getJdbcTemplate().update(INSERT_INTO_FILE,
new Object[] { file.getName(), file.getContent(), parentId });
FieldUtils.setProtectedFieldValue("id", file, obtainPrimaryKey());
}
else {
throw new IllegalArgumentException("Unsupported AbstractElement");
}
}
public void delete(File file) {
Assert.notNull(file, "File required");
Assert.notNull(file.getId(), "File ID required");
getJdbcTemplate().update(DELETE_FROM_FILE, new Object[] { file.getId() });
}
/** Executes recursive SQL as needed to build a full Directory hierarchy of objects */
private Directory getDirectoryWithImmediateParentPopulated(final Long id) {
return getJdbcTemplate().queryForObject(SELECT_FROM_DIRECTORY_SINGLE,
new Object[] { id }, new RowMapper<Directory>() {
public Directory mapRow(ResultSet rs, int rowNumber)
throws SQLException {
Long parentDirectoryId = new Long(rs
.getLong("parent_directory_id"));
Directory parentDirectory = Directory.ROOT_DIRECTORY;
if (parentDirectoryId != null
&& !parentDirectoryId.equals(new Long(-1))) {
// Need to go and lookup the parent, so do that first
parentDirectory = getDirectoryWithImmediateParentPopulated(parentDirectoryId);
}
Directory directory = new Directory(rs
.getString("directory_name"), parentDirectory);
FieldUtils.setProtectedFieldValue("id", directory,
new Long(rs.getLong("id")));
return directory;
}
});
}
public AbstractElement[] findElements(Directory directory) {
Assert.notNull(directory,
"Directory required (the ID can be null to refer to root)");
if (directory.getId() == null) {
List<Directory> directories = getJdbcTemplate().query(
SELECT_FROM_DIRECTORY_NULL, new RowMapper<Directory>() {
public Directory mapRow(ResultSet rs, int rowNumber)
throws SQLException {
return getDirectoryWithImmediateParentPopulated(new Long(rs
.getLong("id")));
}
});
return (AbstractElement[]) directories.toArray(new AbstractElement[] {});
}
List<AbstractElement> directories = getJdbcTemplate().query(
SELECT_FROM_DIRECTORY, new Object[] { directory.getId() },
new RowMapper<AbstractElement>() {
public Directory mapRow(ResultSet rs, int rowNumber)
throws SQLException {
return getDirectoryWithImmediateParentPopulated(new Long(rs
.getLong("id")));
}
});
List<File> files = getJdbcTemplate().query(SELECT_FROM_FILE,
new Object[] { directory.getId() }, new RowMapper<File>() {
public File mapRow(ResultSet rs, int rowNumber) throws SQLException {
Long parentDirectoryId = new Long(rs
.getLong("parent_directory_id"));
Directory parentDirectory = null;
if (parentDirectoryId != null) {
parentDirectory = getDirectoryWithImmediateParentPopulated(parentDirectoryId);
}
File file = new File(rs.getString("file_name"), parentDirectory);
FieldUtils.setProtectedFieldValue("id", file,
new Long(rs.getLong("id")));
return file;
}
});
// Add the File elements after the Directory elements
directories.addAll(files);
return (AbstractElement[]) directories.toArray(new AbstractElement[] {});
}
public void update(File file) {
Assert.notNull(file, "File required");
Assert.notNull(file.getId(), "File ID required");
getJdbcTemplate().update(UPDATE_FILE,
new Object[] { file.getContent(), file.getId() });
}
}
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms;
import org.springframework.util.Assert;
/**
*
* @author Ben Alex
*/
public class File extends AbstractElement {
/** Content of the file, which can be null */
private String content;
public File(String name, Directory parent) {
super(name, parent);
Assert.isTrue(!parent.equals(Directory.ROOT_DIRECTORY),
"Cannot insert File into root directory");
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String toString() {
return "File[fullName='" + getFullName() + "'; name='" + getName() + "'; id='"
+ getId() + "'; content=" + getContent() + "'; parent='" + getParent()
+ "']";
}
}
@@ -0,0 +1,117 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms.secured;
import javax.sql.DataSource;
import org.springframework.security.acls.domain.BasePermission;
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.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.context.SecurityContextHolder;
import org.springframework.util.Assert;
import sample.dms.AbstractElement;
import sample.dms.DataSourcePopulator;
import sample.dms.DocumentDao;
public class SecureDataSourcePopulator extends DataSourcePopulator {
private MutableAclService aclService;
public SecureDataSourcePopulator(DataSource dataSource,
SecureDocumentDao documentDao, MutableAclService aclService) {
super(dataSource, documentDao);
Assert.notNull(aclService, "MutableAclService required");
this.aclService = aclService;
}
protected void addPermission(DocumentDao documentDao, AbstractElement element,
String recipient, int level) {
Assert.notNull(documentDao, "DocumentDao required");
Assert.isInstanceOf(SecureDocumentDao.class, documentDao,
"DocumentDao should have been a SecureDocumentDao");
Assert.notNull(element, "Element required");
Assert.hasText(recipient, "Recipient required");
Assert.notNull(SecurityContextHolder.getContext().getAuthentication(),
"SecurityContextHolder must contain an Authentication");
// We need SecureDocumentDao to assign different permissions
// SecureDocumentDao dao = (SecureDocumentDao) documentDao;
// We need to construct an ACL-specific Sid. Note the prefix contract is defined
// on the superclass method's JavaDocs
Sid sid = null;
if (recipient.startsWith("ROLE_")) {
sid = new GrantedAuthoritySid(recipient);
}
else {
sid = new PrincipalSid(recipient);
}
// We need to identify the target domain object and create an ObjectIdentity for
// it
// This works because AbstractElement has a "getId()" method
ObjectIdentity identity = new ObjectIdentityImpl(element);
// ObjectIdentity identity = new ObjectIdentityImpl(element.getClass(),
// element.getId()); // equivalent
// Next we need to create a Permission
Permission permission = null;
if (level == LEVEL_NEGATE_READ || level == LEVEL_GRANT_READ) {
permission = BasePermission.READ;
}
else if (level == LEVEL_GRANT_WRITE) {
permission = BasePermission.WRITE;
}
else if (level == LEVEL_GRANT_ADMIN) {
permission = BasePermission.ADMINISTRATION;
}
else {
throw new IllegalArgumentException("Unsupported LEVEL_");
}
// Attempt to retrieve the existing ACL, creating an ACL if it doesn't already
// exist for this ObjectIdentity
MutableAcl acl = null;
try {
acl = (MutableAcl) aclService.readAclById(identity);
}
catch (NotFoundException nfe) {
acl = aclService.createAcl(identity);
Assert.notNull(acl, "Acl could not be retrieved or created");
}
// Now we have an ACL, add another ACE to it
if (level == LEVEL_NEGATE_READ) {
acl.insertAce(acl.getEntries().size(), permission, sid, false); // not
// granting
}
else {
acl.insertAce(acl.getEntries().size(), permission, sid, true); // granting
}
// Finally, persist the modified ACL
aclService.updateAcl(acl);
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.dms.secured;
import sample.dms.DocumentDao;
/**
* Extends the {@link DocumentDao} and introduces ACL-related methods.
*
* @author Ben Alex
*
*/
public interface SecureDocumentDao extends DocumentDao {
/**
* @return all the usernames existing in the system.
*/
public String[] getUsers();
}

Some files were not shown because too many files have changed in this diff Show More