Prechádzať zdrojové kódy

SEC-965: Added integration tests for CAS Sample Application

Rob Winch 14 rokov pred
rodič
commit
761d5af6ec

+ 62 - 11
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,10 +38,15 @@ dependencies {
             project(':spring-security-config'),
             "org.slf4j:jcl-over-slf4j:$slf4jVersion",
             "ch.qos.logback:logback-classic:$logbackVersion"
-}
-
 
-def keystore = "$rootDir/samples/certificates/server.jks"
+    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"
+}
 
 [jettyRun, jettyRunWar]*.configure {
     contextPath = "/cas-sample"
@@ -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
+}

+ 45 - 0
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')
+    }
+}

+ 115 - 0
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('<h1>Secure Page</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
+    }
+}

+ 111 - 0
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
+    }
+}

+ 124 - 0
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
+    }
+}

+ 31 - 0
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") }
+    }
+}

+ 27 - 0
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.*/)) }
+}

+ 32 - 0
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 }
+    }
+}

+ 32 - 0
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') }
+    }
+}

+ 35 - 0
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') }
+    }
+}

+ 46 - 0
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"
+    }
+}

+ 33 - 0
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 }
+    }
+}