diff --git a/samples/cas/cas.gradle b/samples/cas/cas.gradle
index b0f171689a..230f2221b5 100644
--- a/samples/cas/cas.gradle
+++ b/samples/cas/cas.gradle
@@ -2,22 +2,35 @@
apply plugin: 'war'
apply plugin: 'jetty'
+apply plugin: 'groovy'
-def excludeModules = ['spring-security-acl', 'jsr250-api', 'ehcache', 'spring-jdbc', 'spring-tx']
+def excludeModules = ['spring-security-acl', 'jsr250-api', 'spring-jdbc', 'spring-tx']
+def jettyVersion = '7.1.6.v20100715'
+def keystore = "$rootDir/samples/certificates/server.jks"
+def password = 'password'
configurations {
casServer
-}
-
-configurations {
excludeModules.each {name ->
runtime.exclude module: name
}
runtime.exclude group: 'org.aspectj'
+
+ integrationTestCompile.extendsFrom groovy
+}
+
+sourceSets.integrationTest {
+ groovy.srcDir file('src/integration-test/groovy')
+}
+
+eclipseClasspath {
+ plusConfigurations += configurations.integrationTestRuntime
}
dependencies {
+ groovy 'org.codehaus.groovy:groovy:1.7.7'
+
casServer "org.jasig.cas:cas-server-webapp:3.4.3.1@war"
runtime project(':spring-security-web'),
@@ -25,11 +38,16 @@ dependencies {
project(':spring-security-config'),
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
"ch.qos.logback:logback-classic:$logbackVersion"
+
+ integrationTestCompile project(':spring-security-cas'),
+ 'org.seleniumhq.selenium:selenium-htmlunit-driver:2.0a7',
+ 'org.spockframework:spock-core:0.4-groovy-1.7',
+ 'org.codehaus.geb:geb-spock:0.5.1',
+ 'commons-httpclient:commons-httpclient:3.1',
+ "org.eclipse.jetty:jetty-server:$jettyVersion",
+ "org.eclipse.jetty:jetty-servlet:$jettyVersion"
}
-
-def keystore = "$rootDir/samples/certificates/server.jks"
-
[jettyRun, jettyRunWar]*.configure {
contextPath = "/cas-sample"
def httpConnector = new org.mortbay.jetty.nio.SelectChannelConnector();
@@ -38,33 +56,66 @@ def keystore = "$rootDir/samples/certificates/server.jks"
def httpsConnector = new org.mortbay.jetty.security.SslSocketConnector();
httpsConnector.port = 8443
httpsConnector.keystore = httpsConnector.truststore = keystore
- httpsConnector.keyPassword = httpsConnector.trustPassword = 'password'
+ httpsConnector.keyPassword = httpsConnector.trustPassword = password
connectors = [httpConnector, httpsConnector]
+ doFirst() {
+ System.setProperty('cas.server.host', casServer.httpsHost)
+ System.setProperty('cas.service.host', jettyRunWar.httpsHost)
+ }
}
-
task casServer (type: org.gradle.api.plugins.jetty.JettyRunWar) {
contextPath = "/cas"
connectors = [new org.mortbay.jetty.security.SslSocketConnector()]
connectors[0].port = 9443
connectors[0].keystore = connectors[0].truststore = keystore
- connectors[0].keyPassword = connectors[0].trustPassword = 'password'
+ connectors[0].keyPassword = connectors[0].trustPassword = password
connectors[0].wantClientAuth = true
connectors[0].needClientAuth = false
webApp = configurations.casServer.resolve().toArray()[0]
doFirst() {
System.setProperty('javax.net.ssl.trustStore', keystore)
- System.setProperty('javax.net.ssl.trustStorePassword', 'password')
+ System.setProperty('javax.net.ssl.trustStorePassword', password)
}
}
task cas (dependsOn: [jettyRunWar, casServer]) {
}
+integrationTest.dependsOn cas
+integrationTest.doFirst {
+ systemProperties['cas.server.host'] = casServer.httpsHost
+ systemProperties['cas.service.host'] = jettyRunWar.httpsHost
+ systemProperties['jar.path'] = jar.archivePath
+ systemProperties['javax.net.ssl.trustStore'] = keystore
+ systemProperties['javax.net.ssl.trustStorePassword'] = password
+}
+
gradle.taskGraph.whenReady {graph ->
if (graph.hasTask(cas)) {
jettyRunWar.dependsOn(casServer)
casServer.daemon = true
}
+ if(graph.hasTask(integrationTest)) {
+ jettyRunWar.daemon = true
+ jettyRunWar.httpConnector.port = availablePort()
+ jettyRunWar.httpsConnector.port = jettyRunWar.httpConnector.confidentialPort = availablePort()
+ casServer.httpsConnector.port = availablePort()
+ }
}
+[casServer,jettyRunWar]*.metaClass*.getHttpsConnector {->
+ delegate.connectors.find { it instanceof org.mortbay.jetty.security.SslSocketConnector }
+}
+[casServer,jettyRunWar]*.metaClass*.getHttpsHost {->
+ "localhost:"+delegate.httpsConnector.port
+}
+jettyRunWar.metaClass.getHttpConnector {->
+ delegate.connectors.find { it instanceof org.mortbay.jetty.nio.SelectChannelConnector }
+}
+def availablePort() {
+ ServerSocket server = new ServerSocket(0)
+ int port = server.localPort
+ server.close()
+ port
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/BaseSpec.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/BaseSpec.groovy
new file mode 100644
index 0000000000..e96229f3ec
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/BaseSpec.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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 BaseSpec extends GebReportingSpec {
+
+ /**
+ * All relative urls will be interpreted against this. The host can change based upon a system property. This
+ * allows for the port to be randomly selected from available ports for CI.
+ */
+ String getBaseUrl() {
+ def host = System.getProperty('cas.service.host', 'localhost:8443')
+ "https://${host}/cas-sample/"
+ }
+
+ /**
+ * Write out responses and screenshots here.
+ */
+ File getReportDir() {
+ new File('build/geb-reports')
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleProxySpec.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleProxySpec.groovy
new file mode 100644
index 0000000000..ec92ec3ac4
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleProxySpec.groovy
@@ -0,0 +1,115 @@
+/*
+ * 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 CasSampleProxySpec extends BaseSpec {
+ 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('
Secure Page
')
+ }
+
+ 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('VERY Secure Page
')
+ }
+
+ 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('VERY Secure Page
')
+ }
+
+ 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
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleSpec.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleSpec.groovy
new file mode 100644
index 0000000000..8c426b330b
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleSpec.groovy
@@ -0,0 +1,111 @@
+/*
+ * 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.junit.runner.RunWith;
+import org.spockframework.runtime.Sputnik;
+import org.springframework.security.samples.cas.pages.*
+
+import spock.lang.Stepwise;
+
+/**
+ * Tests the CAS sample application using service tickets.
+ *
+ * @author Rob Winch
+ */
+@Stepwise
+class CasSampleSpec extends BaseSpec {
+
+ 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'
+ to ExtremelySecurePage
+ then: 'The login page is displayed'
+ at LoginPage
+ }
+
+ def 'authenticate attempt with invaid ticket fails'() {
+ when: 'present invalid ticket'
+ go "j_spring_cas_security_check?ticket=invalid"
+ then: 'the login failed page is displayed'
+ println driver.pageSource
+ $("h2").text() == 'Login to CAS failed!'
+ }
+
+ def 'access secure page with unauthenticated user requires login'() {
+ when: 'Unauthenticated user accesses the secure page'
+ to 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 extremely secure page with ROLE_USER is denied'() {
+ when: 'User with ROLE_USER accesses extremely secure page'
+ to 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()
+ to 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()
+ to ExtremelySecurePage
+ then: 'login page is displayed'
+ at LoginPage
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/JettyCasService.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/JettyCasService.groovy
new file mode 100644
index 0000000000..438ac0d2c0
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/JettyCasService.groovy
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/modules/NavModule.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/modules/NavModule.groovy
new file mode 100644
index 0000000000..d6f8ecc5c9
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/modules/NavModule.groovy
@@ -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") }
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/AccessDeniedPage.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/AccessDeniedPage.groovy
new file mode 100644
index 0000000000..29d5d31be7
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/AccessDeniedPage.groovy
@@ -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.*/)) }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/ExtremelySecurePage.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/ExtremelySecurePage.groovy
new file mode 100644
index 0000000000..b4e6ff11a7
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/ExtremelySecurePage.groovy
@@ -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 }
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/HomePage.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/HomePage.groovy
new file mode 100644
index 0000000000..65020581a1
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/HomePage.groovy
@@ -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') }
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LocalLogoutPage.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LocalLogoutPage.groovy
new file mode 100644
index 0000000000..dacae6327f
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LocalLogoutPage.groovy
@@ -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') }
+ }
+}
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LoginPage.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LoginPage.groovy
new file mode 100644
index 0000000000..b20679db8a
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LoginPage.groovy
@@ -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 = { driver.currentUrl == 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 an randomly available port for CI.
+ * @return
+ */
+ private static String loginUrl() {
+ def host = System.getProperty('cas.server.host', 'localhost:9443')
+ "https://${host}/cas/login"
+ }
+}
\ No newline at end of file
diff --git a/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/SecurePage.groovy b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/SecurePage.groovy
new file mode 100644
index 0000000000..3231c01eab
--- /dev/null
+++ b/samples/cas/src/integration-test/groovy/org/springframework/security/samples/cas/pages/SecurePage.groovy
@@ -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 }
+ }
+}
\ No newline at end of file