├── settings.gradle ├── src ├── test │ ├── docker │ │ ├── cas_server │ │ │ ├── cas │ │ │ │ ├── etc │ │ │ │ │ └── cas │ │ │ │ │ │ ├── config │ │ │ │ │ │ ├── application.yml │ │ │ │ │ │ └── cas.properties │ │ │ │ │ │ └── services │ │ │ │ │ │ └── SampleService-1.json │ │ │ │ ├── maven │ │ │ │ │ └── maven-wrapper.properties │ │ │ │ ├── build.sh │ │ │ │ ├── pom.xml │ │ │ │ └── mvnw │ │ │ └── Dockerfile │ │ ├── shib_idp │ │ │ ├── shibboleth-idp │ │ │ │ ├── credentials │ │ │ │ │ ├── sealer.kver │ │ │ │ │ ├── sealer.jks │ │ │ │ │ ├── idp-browser.p12 │ │ │ │ │ ├── idp-backchannel.p12 │ │ │ │ │ ├── idp-encryption.crt │ │ │ │ │ ├── idp-signing.crt │ │ │ │ │ ├── idp-backchannel.crt │ │ │ │ │ ├── idp-signing.key │ │ │ │ │ └── idp-encryption.key │ │ │ │ ├── conf │ │ │ │ │ ├── ldap.properties │ │ │ │ │ ├── metadata-providers.xml │ │ │ │ │ ├── authn │ │ │ │ │ │ └── general-authn.xml │ │ │ │ │ └── idp.properties │ │ │ │ ├── metadata │ │ │ │ │ ├── sp-metadata.xml │ │ │ │ │ └── idp-metadata.xml │ │ │ │ └── webapp │ │ │ │ │ └── WEB-INF │ │ │ │ │ └── web.xml │ │ │ └── Dockerfile │ │ ├── ldap │ │ │ ├── users.ldif │ │ │ ├── ds-setup.inf │ │ │ └── Dockerfile │ │ └── docker-compose.yaml │ └── java │ │ └── net │ │ └── unicon │ │ └── idp │ │ └── externalauth │ │ └── ShibcasAuthServletTest.java └── main │ └── java │ └── net │ └── unicon │ └── idp │ ├── authn │ └── provider │ │ └── extra │ │ ├── CasMultifactorRefedsToDuoSecurityAuthnMethodParameterBuilder.java │ │ ├── CasMultifactorRefedsToGoogleAuthenticatorAuthnMethodParameterBuilder.java │ │ ├── IParameterBuilder.java │ │ ├── EntityIdParameterBuilder.java │ │ └── CasAuthnMethodParameterBuilder.java │ └── externalauth │ ├── CasToShibTranslator.java │ ├── AuthenticatedNameTranslator.java │ ├── CasDuoSecurityRefedsAuthnMethodTranslator.java │ └── ShibcasAuthServlet.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .travis.yml ├── gradle.properties ├── IDP_HOME └── edit-webapp │ └── no-conversation-state.jsp ├── gradlew.bat ├── gradlew ├── README.md └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='shib-cas-authenticator' 2 | 3 | -------------------------------------------------------------------------------- /src/test/docker/cas_server/cas/etc/cas/config/application.yml: -------------------------------------------------------------------------------- 1 | info: 2 | description: CAS Configuration -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/sealer.kver: -------------------------------------------------------------------------------- 1 | #Fri Dec 11 02:20:32 UTC 2015 2 | CurrentVersion=1 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unicon/shib-cas-authn3/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/sealer.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unicon/shib-cas-authn3/HEAD/src/test/docker/shib_idp/shibboleth-idp/credentials/sealer.jks -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/idp-browser.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unicon/shib-cas-authn3/HEAD/src/test/docker/shib_idp/shibboleth-idp/credentials/idp-browser.p12 -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/idp-backchannel.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unicon/shib-cas-authn3/HEAD/src/test/docker/shib_idp/shibboleth-idp/credentials/idp-backchannel.p12 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | .DS_Store 8 | *.iws 9 | .gradle/ 10 | .sonar-ide.properties 11 | build/ 12 | /bin 13 | .classpath 14 | .project 15 | .idea 16 | *.iml 17 | *.ipr 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: required 3 | branches: 4 | only: 5 | - master 6 | jdk: 7 | - oraclejdk8 8 | cache: 9 | directories: 10 | - '$HOME/.gradle/caches' 11 | 12 | script: ./gradlew clean build 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | gradleVersion=4.5 2 | 3 | version=3.3.0 4 | 5 | casClientVersion=3.6.0 6 | commonLangVersion=2.5 7 | junitVersion=4.12 8 | mockitoVersion=1.9.5 9 | powermockVersion=1.6.1 10 | servletVersion=3.0.1 11 | shibIdpVersion=3.4.6 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-bin.zip 6 | -------------------------------------------------------------------------------- /src/test/docker/cas_server/cas/maven/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Maven download properties 2 | #Tue Jul 12 01:10:05 MST 2016 3 | distributionUrl=https\://repository.apache.org/content/repositories/releases/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 4 | -------------------------------------------------------------------------------- /src/test/docker/cas_server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:centos7 2 | 3 | MAINTAINER jgasper@unicon.net 4 | 5 | RUN yum install -y java which 6 | 7 | COPY cas/ /opt/cas 8 | 9 | WORKDIR /opt/cas 10 | 11 | CMD export JAVA_HOME=$(readlink -f /usr/bin/javac | sed "s:/bin/javac::"); ./build.sh 12 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM unicon/shibboleth-idp:3.3.1 2 | 3 | MAINTAINER jgasper@unicon.net 4 | 5 | COPY shibboleth-idp/ /opt/shibboleth-idp 6 | 7 | ADD https://github.com/Unicon/shib-cas-authn3/releases/download/v3.2.0/cas-client-core-3.4.1.jar /opt/shibboleth-idp/webapp/WEB-INF/lib -------------------------------------------------------------------------------- /src/test/docker/cas_server/cas/etc/cas/services/SampleService-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "@class": "org.apereo.cas.services.RegexRegisteredService", 3 | "serviceId": "^(https|imaps|http)://.*", 4 | "name": "SampleService", 5 | "id": 1, 6 | "description": "Sample CAS client application", 7 | "evaluationOrder": 0 8 | } -------------------------------------------------------------------------------- /src/test/docker/ldap/users.ldif: -------------------------------------------------------------------------------- 1 | dn: uid=jsmith,ou=People,dc=example,dc=edu 2 | objectClass: organizationalPerson 3 | objectClass: person 4 | objectClass: top 5 | objectClass: inetOrgPerson 6 | objectClass: inetUser 7 | objectClass: passwordObject 8 | givenName: Joe 9 | uid: jsmith 10 | sn: Smith 11 | cn: John Smith 12 | mail: jsmith@example.edu 13 | userPassword: password 14 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/authn/provider/extra/CasMultifactorRefedsToDuoSecurityAuthnMethodParameterBuilder.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.authn.provider.extra; 2 | 3 | public class CasMultifactorRefedsToDuoSecurityAuthnMethodParameterBuilder extends CasAuthnMethodParameterBuilder { 4 | @Override 5 | protected String getCasAuthenticationMethodFor(final String authnMethod) { 6 | if (isMultifactorRefedsProfile(authnMethod)) { 7 | return "mfa-duo"; 8 | } 9 | return null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/authn/provider/extra/CasMultifactorRefedsToGoogleAuthenticatorAuthnMethodParameterBuilder.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.authn.provider.extra; 2 | 3 | public class CasMultifactorRefedsToGoogleAuthenticatorAuthnMethodParameterBuilder extends CasAuthnMethodParameterBuilder { 4 | @Override 5 | protected String getCasAuthenticationMethodFor(final String authnMethod) { 6 | if (isMultifactorRefedsProfile(authnMethod)) { 7 | return "mfa-gauth"; 8 | } 9 | return null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | cas_server: 5 | build: ./cas_server/ 6 | depends_on: 7 | - ldap 8 | ports: 9 | - 8443:8443 10 | volumes: 11 | - ~/.m2:/root/.m2 12 | - /dev/urandom:/dev/random 13 | 14 | shib_idp: 15 | build: ./shib_idp/ 16 | depends_on: 17 | - ldap 18 | environment: 19 | - JETTY_MAX_HEAP=64m 20 | - JETTY_BROWSER_SSL_KEYSTORE_PASSWORD=password 21 | - JETTY_BACKCHANNEL_SSL_KEYSTORE_PASSWORD=password 22 | ports: 23 | - 443:4443 24 | volumes: 25 | - /dev/urandom:/dev/random 26 | 27 | ldap: 28 | build: ./ldap/ 29 | ports: 30 | - 389:389 31 | -------------------------------------------------------------------------------- /src/test/docker/ldap/ds-setup.inf: -------------------------------------------------------------------------------- 1 | [General] 2 | AdminDomain = example.edu 3 | ConfigDirectoryAdminID = admin 4 | ConfigDirectoryAdminPwd = admin 5 | ConfigDirectoryLdapURL = ldap://localhost:389/o=NetscapeRoot 6 | FullMachineName = localhost 7 | ServerRoot = /usr/lib64/dirsrv 8 | SuiteSpotGroup = nobody 9 | SuiteSpotUserID = nobody 10 | 11 | [admin] 12 | Port = 9830 13 | ServerAdminID = admin 14 | ServerAdminPwd = admin 15 | ServerIpAddress = 0.0.0.0 16 | SysUser = nobody 17 | 18 | [slapd] 19 | AddOrgEntries = Yes 20 | AddSampleEntries = No 21 | InstallLdifFile = suggest 22 | RootDN = cn=Directory Manager 23 | RootDNPwd = password 24 | ServerIdentifier = dir 25 | ServerPort = 389 26 | SlapdConfigForMC = yes 27 | Suffix = dc=example,dc=edu 28 | UseExistingMC = No 29 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/authn/provider/extra/IParameterBuilder.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.authn.provider.extra; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | /** 6 | * This interface defines the interface a custom parameter builder must adopt. The implementing class needs to build a single string 7 | * that is of the form: paramName=paramValue 8 | * The resulting param name-value pair will be sent to CAS in the redirect to /login. 9 | * @author chasegawa@unicon.net 10 | */ 11 | public interface IParameterBuilder { 12 | 13 | /** 14 | * Builder should build a string to be added to the param list of a new request. The original request should not be modified. 15 | * @param request The original request. 16 | * @return a string of the form: paramName=value 17 | */ 18 | String getParameterString(HttpServletRequest request, String authenticationKey); 19 | } 20 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/idp-encryption.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDEzCCAfugAwIBAgIUG6Nn1rlERS1vsi88tcdzSYX0oqAwDQYJKoZIhvcNAQEL 3 | BQAwFTETMBEGA1UEAwwKaWRwdGVzdGJlZDAeFw0xNTEyMTEwMjIwMTRaFw0zNTEy 4 | MTEwMjIwMTRaMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQCBXv0o3fmT8iluyLjJ4lBAVCW+ZRVyEXPYQuRi7vfD 6 | cO4a6d1kxiJLsaK0W88VNxjFQRr8PgDkWr28vwoH1rgk4pLsszLD48DBzD942peJ 7 | l/S6FnsIJjmaHcBh4pbNhU4yowu63iKkvttrcZAEbpEro6Z8CziWEx8sywoaYEQG 8 | ifPkr9ORV6Cn3txq+9gMBePG41GrtZrUGIu+xrndL0Shh4Pq0eq/9MAsVlIIXEa8 9 | 9WfH8J2kFcTOfoWtIc70b7TLZQsx4YnNcnrGLSUEcstFyPLX+Xtv5SNZF89OOIxX 10 | VNjNvgE5DbJb9hMM4UAFqI+1bo9QqtxwThjc/sOvIxzNAgMBAAGjWzBZMB0GA1Ud 11 | DgQWBBStTyogRPuAVG6q7yPyav1uvE+7pTA4BgNVHREEMTAvggppZHB0ZXN0YmVk 12 | hiFodHRwczovL2lkcHRlc3RiZWQvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQEL 13 | BQADggEBAFMfoOv+oISGjvamq7+Y4G7ep5vxlAPeK3RATYPYvAmyH946qZXh98ni 14 | QXyuqZW5P5eEt86toY45IwDU5r09SKwHughEe99iiEkxh0mb2qo84qX9/qcg+kyN 15 | jeLd/OSyolpUCEFNwOFcog7pj7Eer+6AHbwTn1Mjb5TBsKwtDMJsaxPvdj0u7M5r 16 | xL/wHkFhn1rCo2QiojzjSlV3yLTh49iTyhE3cG+RxaNKDCxhp0jSSLX1BW/ZoPA8 17 | +PMJEA+Q0QbyRD8aJOHN5O8jGxCa/ZzcOnYVL6AsEXoDiY3vAUYh1FUonOWw0m9H 18 | p+tGUbGS2l873J5PrsbpeKEVR/IIoKo= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/idp-signing.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDFDCCAfygAwIBAgIVAN3vv+b7KN5Se9m1RZsCllp/B/hdMA0GCSqGSIb3DQEB 3 | CwUAMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwHhcNMTUxMjExMDIyMDE0WhcNMzUx 4 | MjExMDIyMDE0WjAVMRMwEQYDVQQDDAppZHB0ZXN0YmVkMIIBIjANBgkqhkiG9w0B 5 | AQEFAAOCAQ8AMIIBCgKCAQEAh91caeY0Q85uhaUyqFwP2bMjwMFxMzRlAoqBHd7g 6 | u6eo4duaeLz1BaoR2XTBpNNvFR5oHH+TkKahVDGeH5+kcnIpxI8JPdsZml1srvf2 7 | Z6dzJsulJZUdpqnngycTkGtZgEoC1vmYVky2BSAIIifmdh6s0epbHnMGLsHzMKfJ 8 | Cb/Q6dYzRWTCPtzE2VMuQqqWgeyMr7u14x/Vqr9RPEFsgY8GIu5jzB6AyUIwrLg+ 9 | MNkv6aIdcHwxYTGL7ijfy6rSWrgBflQoYRYNEnseK0ZHgJahz4ovCag6wZAoPpBs 10 | uYlY7lEr89Ucb6NHx3uqGMsXlDFdE4QwfDLLhCYHPvJ0uwIDAQABo1swWTAdBgNV 11 | HQ4EFgQUAkOgED3iYdmvQEOMm6u/JmD/UTQwOAYDVR0RBDEwL4IKaWRwdGVzdGJl 12 | ZIYhaHR0cHM6Ly9pZHB0ZXN0YmVkL2lkcC9zaGliYm9sZXRoMA0GCSqGSIb3DQEB 13 | CwUAA4IBAQBIdd4YWlnvJjql8+zKKgmWgIY7U8DA8e6QcbAf8f8cdE33RSnjI63X 14 | sv/y9GfmbAVAD6RIAXPFFeRYJ08GOxGI9axfNaKdlsklJ9bk4ducHqgCSWYVer3s 15 | RQBjxyOfSTvk9YCJvdJVQRJLcCvxwKakFCsOSnV3t9OvN86Ak+fKPVB5j2fM/0fZ 16 | Kqjn3iqgdNPTLXPsuJLJO5lITRiBa4onmVelAiCstI9PQiaEck+oAHnMTnC9JE/B 17 | DHv3e4rwq3LznlqPw0GSd7xqNTdMDwNOWjkuOr3sGpWS8ms/ZHHXV1Vd22uPe70i 18 | s00xrv14zLifcc8oj5DYzOhYRifRXgHX 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/idp-backchannel.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDEzCCAfugAwIBAgIUS9SuTXwsFVVG+LjOEAbLqqT/el0wDQYJKoZIhvcNAQEL 3 | BQAwFTETMBEGA1UEAwwKaWRwdGVzdGJlZDAeFw0xNTEyMTEwMjIwMjZaFw0zNTEy 4 | MTEwMjIwMjZaMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQCMAoDHx8xCIfv/6QKqt9mcHYmEJ8y2dKprUbpdcOjH 6 | YvNPIl/lHPsUyrb+Nc+q2CDeiWjVk1mWYq0UpIwpBMuw1H6+oOqr4VQRi65pin0M 7 | SfE0MWIaFo5FPvpvoptkHD4gvREbm4swyXGMczcMRfqgalFXhUD2wz8W3XAM5Cq2 8 | 03XeJbj6TwjvKatG5XPdeUe2FBGuOO2q54L1hcIGnLMCQrg7D31lR13PJbjnJ0No 9 | 5C3k8TPuny6vJsBC03GNLNKfmrKVTdzr3VKp1uay1G3DL9314fgmbl8HA5iRQmy+ 10 | XInUU6/8NXZSF59p3ITAOvZQeZsbJjg5gGDip5OZo9YlAgMBAAGjWzBZMB0GA1Ud 11 | DgQWBBRPlM4VkKZ0U4ec9GrIhFQl0hNbLDA4BgNVHREEMTAvggppZHB0ZXN0YmVk 12 | hiFodHRwczovL2lkcHRlc3RiZWQvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQEL 13 | BQADggEBAIZ0a1ov3my3ljJG588I/PHx+TxAWONWmpKbO9c/qI3Drxk4oRIffiac 14 | ANxdvtabgIzrlk5gMMisD7oyqHJiWgKv5Bgctd8w3IS3lLl7wHX65mTKQRXniG98 15 | NIjkvfrhe2eeJxecOqnDI8GOhIGCIqZUn8ShdM/yHjhQ2Mh0Hj3U0LlKvnmfGSQl 16 | j0viGwbFCaNaIP3zc5UmCrdE5h8sWL3Fu7ILKM9RyFa2ILHrJScV9t623IcHffHP 17 | IeaY/WtuapsrqRFxuQL9QFWN0FsRIdLmjTq+00+B/XnnKRKFBuWfjhHLF/uu8f+E 18 | t6Lf23Kb8yD6ZR7dihMZAGHnYQ/hlhM= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /src/test/docker/ldap/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:centos7 2 | 3 | MAINTAINER John Gasper 4 | 5 | RUN yum install -y epel-release \ 6 | && yum update -y \ 7 | && yum install -y 389-ds-base 389-adminutil \ 8 | && yum clean all 9 | 10 | COPY ds-setup.inf / 11 | COPY users.ldif / 12 | 13 | # The 389-ds setup will fail because the hostname can't reliable be determined, so we'll bypass it and then install. 14 | RUN useradd ldapadmin \ 15 | && rm -fr /var/lock /usr/lib/systemd/system \ 16 | # The 389-ds setup will fail because the hostname can't reliable be determined, so we'll bypass it and then install. \ 17 | && sed -i 's/checkHostname {/checkHostname {\nreturn();/g' /usr/lib64/dirsrv/perl/DSUtil.pm \ 18 | # Not doing SELinux \ 19 | && sed -i 's/updateSelinuxPolicy($inf);//g' /usr/lib64/dirsrv/perl/* \ 20 | # Do not restart at the end \ 21 | && sed -i '/if (@errs = startServer($inf))/,/}/d' /usr/lib64/dirsrv/perl/* \ 22 | && setup-ds.pl --silent --file /ds-setup.inf \ 23 | && /usr/sbin/ns-slapd -D /etc/dirsrv/slapd-dir \ 24 | && sleep 5 \ 25 | && ldapadd -H ldap:/// -f /users.ldif -x -D "cn=Directory Manager" -w password 26 | 27 | EXPOSE 389 28 | 29 | CMD /usr/sbin/ns-slapd -D /etc/dirsrv/slapd-dir && tail -F /var/log/dirsrv/slapd-dir/access 30 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/externalauth/CasToShibTranslator.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.externalauth; 2 | 3 | import org.jasig.cas.client.validation.Assertion; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | /** 9 | * This interface defines the public interface for a class that will translate the information from CAS to Shib. The translator 10 | * should only push details into the request and should NOT attempt to call 11 | * AuthenticationEngine.returnToAuthenticationEngine(request, response); 12 | *

13 | * Instance of this type should implement hashcode and equals. 14 | * 15 | * @author chasegawa @unicon.net 16 | */ 17 | public interface CasToShibTranslator { 18 | /** 19 | * Do the needed translation. 20 | * 21 | * @param request The HttpServletRequest object 22 | * @param response The HttpServletResponse object 23 | * @param assertion The CAS Assertion after validating the CAS ticket 24 | * @param authenticationKey the authentication key 25 | * @throws Exception the exception 26 | */ 27 | void doTranslation(HttpServletRequest request, HttpServletResponse response, Assertion assertion, String authenticationKey) throws Exception; 28 | } 29 | -------------------------------------------------------------------------------- /src/test/docker/cas_server/cas/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | function copy() { 5 | echo -e "Creating configuration directory under /etc/cas" 6 | mkdir -p /etc/cas/config 7 | 8 | echo -e "Copying configuration files from etc/cas to /etc/cas" 9 | cp -rfv etc/cas/* /etc/cas 10 | } 11 | 12 | function help() { 13 | echo "Usage: build.sh [copy|clean|package|run|debug|bootrun]" 14 | } 15 | 16 | function clean() { 17 | ./mvnw clean "$@" 18 | } 19 | 20 | function package() { 21 | ./mvnw clean package -T 5 "$@" 22 | copy 23 | } 24 | 25 | function bootrun() { 26 | ./mvnw clean package spring-boot:run -T 5 "$@" 27 | } 28 | 29 | function debug() { 30 | package && java -Xdebug -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n -jar target/cas.war 31 | } 32 | 33 | function run() { 34 | package && java -jar target/cas.war 35 | } 36 | 37 | if [ $# -eq 0 ]; then 38 | echo -e "No commands provided. Defaulting to [run]\n" 39 | run 40 | exit 0 41 | fi 42 | 43 | 44 | case "$1" in 45 | "copy") 46 | copy 47 | ;; 48 | "clean") 49 | shift 50 | clean "$@" 51 | ;; 52 | "package") 53 | shift 54 | package "$@" 55 | ;; 56 | "bootrun") 57 | shift 58 | bootrun "$@" 59 | ;; 60 | "run") 61 | run "$@" 62 | ;; 63 | *) 64 | help 65 | ;; 66 | esac 67 | 68 | -------------------------------------------------------------------------------- /IDP_HOME/edit-webapp/no-conversation-state.jsp: -------------------------------------------------------------------------------- 1 | <%@ page pageEncoding="UTF-8" %> 2 | <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> 3 | 4 | 5 | 6 | 7 | <spring:message code="root.title" text="Shibboleth IdP" /> 8 | 9 | 10 | 11 | 12 |

13 |
14 |
15 | " alt=""> 16 |
17 | 18 |
19 |

20 |
21 |
22 | 23 |
24 | 27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/docker/cas_server/cas/etc/cas/config/cas.properties: -------------------------------------------------------------------------------- 1 | cas.server.name=http://localhost:8181 2 | cas.server.prefix=${cas.server.name}/cas 3 | 4 | server.port=8443 5 | server.ssl.enabled=false 6 | #server.ssl.keyStore=file:/etc/cas/thekeystore 7 | #server.ssl.keyStorePassword=changeit 8 | #server.ssl.keyPassword=changeit 9 | 10 | cas.adminPagesSecurity.ip=127\.0\.0\.1 11 | 12 | #cas.host.name= 13 | 14 | #Must be left blank 15 | cas.authn.accept.users= 16 | 17 | cas.authn.ldap[0].type=AUTHENTICATED 18 | 19 | cas.authn.ldap[0].ldapUrl=ldap://ldap 20 | cas.authn.ldap[0].useSsl=false 21 | cas.authn.ldap[0].useStartTls=false 22 | cas.authn.ldap[0].connectTimeout=3000 23 | cas.authn.ldap[0].baseDn=ou=People,DC=example,DC=edu 24 | cas.authn.ldap[0].userFilter=uid={user} 25 | cas.authn.ldap[0].subtreeSearch=true 26 | # cas.authn.ldap[0].usePasswordPolicy=true 27 | cas.authn.ldap[0].bindDn=cn=Directory Manager 28 | cas.authn.ldap[0].bindCredential=password 29 | 30 | # cas.authn.ldap[0].enhanceWithEntryResolver=true 31 | # cas.authn.ldap[0].dnFormat=uid=%s,ou=people,dc=example,dc=org 32 | cas.authn.ldap[0].principalAttributeId=uid 33 | # cas.authn.ldap[0].principalAttributePassword=userPassword 34 | cas.authn.ldap[0].principalAttributeList=givenName,sn 35 | # cas.authn.ldap[0].allowMultiplePrincipalAttributeValues=true 36 | # cas.authn.ldap[0].additionalAttributes= 37 | # cas.authn.ldap[0].credentialCriteria= 38 | 39 | cas.serviceRegistry.config.location=file:/etc/cas/services 40 | 41 | # Ticket Grant Cookie Settings 42 | # cas.tgc.signingKey= 43 | # cas.tgc.encryptionKey= 44 | # cas.webflow.signing.key= 45 | # cas.webflow.signing.keySize=512 46 | # cas.webflow.encryption.keySize=16 47 | # cas.webflow.encryption.key= -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/idp-signing.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAh91caeY0Q85uhaUyqFwP2bMjwMFxMzRlAoqBHd7gu6eo4dua 3 | eLz1BaoR2XTBpNNvFR5oHH+TkKahVDGeH5+kcnIpxI8JPdsZml1srvf2Z6dzJsul 4 | JZUdpqnngycTkGtZgEoC1vmYVky2BSAIIifmdh6s0epbHnMGLsHzMKfJCb/Q6dYz 5 | RWTCPtzE2VMuQqqWgeyMr7u14x/Vqr9RPEFsgY8GIu5jzB6AyUIwrLg+MNkv6aId 6 | cHwxYTGL7ijfy6rSWrgBflQoYRYNEnseK0ZHgJahz4ovCag6wZAoPpBsuYlY7lEr 7 | 89Ucb6NHx3uqGMsXlDFdE4QwfDLLhCYHPvJ0uwIDAQABAoIBAGpInLarQ0+X+ZXK 8 | +aoC+tNO9DUiHq/z2OD4ALGhXSTvr4mgBWNWTkc0F+qJD8MlM8zNkJxaoNGTyLjY 9 | Z95NQJPXAx2k15HwCENdulvV1oiX0dkTjgmscRmj6FwNAZ0EnWtien45mxZHxEyW 10 | FkbB9+OHc6JzNvzG9ps1Vk1FtFtO8w4exXuJVATJeArQmMvAUHMJYPb7Qs+/NX9R 11 | RlDvFfXDzQv5eAzudT1SyFSa5W+Bsw6BtEoeiqdp+xQh4yc733nwN7KG2Z/TpGse 12 | jVe6akbULuCXOe9uPa7kv8hnQEUH38QVlIw8pK1SsgNq7S0U44WU5uF8gbDBYle/ 13 | OoPBvSECgYEAvCwVEa8ryLzee5FaX4PBxk10lEl/Yp9jC88wPUQ+ZpfniIIQIfwl 14 | csRE9D3/dJOVAxw/Ac32F72SLVDzLabAhlBRINYLB0ZkVuJi1CIoDHIf9nfh/pOx 15 | b96VMUe/mpAL4hZnZkmBKjesX5URPEKtBD0aSeCw9aFqhORjRrxCJg0CgYEAuNaD 16 | LOuTPKsC6nxRtiL9r0CA5gCCdpALxwJA7wHAeh03i5xmy61i4iOMaunxKZhG+nzz 17 | PhcI8Uhwwk+l3tbYAf1rrtmMKNcyjy+UqWXGt4ZkWFlIyIungyLiH9L32IMhXNF0 18 | fBgOZNtFTmQBU18a78uIir9xASUbtaakzOtJ2+cCgYAgfawVpZ11x8bSp0Jng6SN 19 | zQn4IMiyCrtbaqb1rTbpGAmOdIa8l4EP0/vkAGB/jIwKQXJPqXR4nO8EjBmxJD3R 20 | 80RO2yaEVw80QVq3Lj6kB4ClWgXXo0DcBB7Wp4DZ+01R+HRaIQ8AbySATIjxUsH1 21 | HWfQoc9sWja+Q4Ew0YjKcQKBgGLoPsdBw8b6B5RsM9lPvgoSbScmbKl/CR5TwWVj 22 | vZhanAd0CLnCrSAvP4tSZf8JAio1xH+cGefrCJOhxTOKKYpfDklBFjQge2iNYHKJ 23 | CJ3aJ0XzePP/bwLIHtJCtOdBvA+L8VYaFVG418xLzT3MrYBVnFoKeTDQp5Q7eQJC 24 | gYJPAoGBAKHcuXWzvXoHKnOg8Ljg2xZ6/SfjwNDIIrpXVTAQifmK3q4+Ua2Q+Cjq 25 | 97tPMxF2bVRcbnCSNKpTMOTrsWs8Z3GpMyCh6XgYMSlclXusDVUkRkPpWj8hVTR4 26 | opm/rxS83hCrTsIX3Il3T8Fpb97kdF+unCiWEaxrPEurjW8lB506 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/credentials/idp-encryption.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAgV79KN35k/Ipbsi4yeJQQFQlvmUVchFz2ELkYu73w3DuGund 3 | ZMYiS7GitFvPFTcYxUEa/D4A5Fq9vL8KB9a4JOKS7LMyw+PAwcw/eNqXiZf0uhZ7 4 | CCY5mh3AYeKWzYVOMqMLut4ipL7ba3GQBG6RK6OmfAs4lhMfLMsKGmBEBonz5K/T 5 | kVegp97cavvYDAXjxuNRq7Wa1BiLvsa53S9EoYeD6tHqv/TALFZSCFxGvPVnx/Cd 6 | pBXEzn6FrSHO9G+0y2ULMeGJzXJ6xi0lBHLLRcjy1/l7b+UjWRfPTjiMV1TYzb4B 7 | OQ2yW/YTDOFABaiPtW6PUKrccE4Y3P7DryMczQIDAQABAoIBAF/IflMllcUtw/Nb 8 | 9USzpIscQh2nJaugtE5nqER/fT1cfU273Mjh0T6NtFMorjec5WAWBe6/0VVAwb3f 9 | C4QmO4xDnFhXjLxwAaT6nfvSi+O5d93XCxxLgNZUNL3ET7a2feELyoF+OdQT4sy3 10 | 9dLyMdVHgtnQTQMAAVLeuQoyP+s+Zax4Gca6ln8QxIIvDoD7NITnpl8887Hghhzl 11 | CvKtRiPRtoI2JTXWgWuLI6xXfVsDvFT+Up+ki9TMLWLACcmMU1d+lUBOKIqhhQHG 12 | +np9iKxVausJwYaLwwT3h8SItNon7ltbV6kcqyZxMAA+uN8CVgIb5UaUrlW7Nvze 13 | 1iUNudkCgYEAxpnBg8YxdEHFSMTqjEOYapn18cs3n32EBPtvPaUcvw3mGC1+ZVx1 14 | 9WqnVsgykBOWI0qSBVF7Kke8yOqgqWtYQUFqLiMgCC9e/QcXnrm/bzAmKDgLVcCR 15 | KzgqU2ECQDkNSS0qeODjLGX4SEabDbLhN59WykHKM0i/RcrbhuvT1BcCgYEApsMD 16 | TFQBaiaEmLVm252piZf8b5g3DrUHeqGktHkHXTW4Iyyn8zEknoiCosk/Tej73zga 17 | cTT3zQgEh63DMC9Ag8IbIJiDpYLMkt1QvZYtq95E/94GVEfRRok6/pyagGYB351R 18 | PXcykrDyy26FSofmtaXU37Wxaj3ow+WROaPgULsCgYEArFoFScG3a2gkuRlDX8TN 19 | wj2o5lTxCbWY2+YEzR+8icWbGQJqPbb3G6uaW8LTtpt44Vm2zWzAEZo+KLMOCNmC 20 | tub5Kd8Lzm6l5brA8dvLWcgUZTT2CU5b7YEJomB+3pNkh0vuHwczv3Ui+j5kE4hY 21 | 0bezT0W3H7iTXhNFXprMs7MCgYEAlIZn75l6URLRUjluzPdVQoktei72CpFNgflp 22 | +ps45dmskRd61mzUkqY+w8G+MiPqANu1IVLtyZz0e+tVRxsuuKsvAg8UYVtn3P5k 23 | pRaWwtaKWeFjfbkhOVOMSa0tJmK0FHfHHZmGX4ReGrXq3YDBCNQUDtOCmn9dSuyy 24 | NcYxSXUCgYB+yo6dg8nyHDSqKDdrQQiAKv7jNsbecQ/rYrt8l0n9FBiwn5R7v6kp 25 | afsimCVou5i06L2Cr5Xs+XSf11KVkDh+qM70ZFubWEsHCDrS1KrxUzfFbrQczKof 26 | qX7ZsBuOT72RwVEa8fpT6IZ6IpOOEPmUid/f2VM2aAcXgaF//vMjxA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/authn/provider/extra/EntityIdParameterBuilder.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.authn.provider.extra; 2 | 3 | import net.shibboleth.idp.authn.ExternalAuthentication; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URLEncoder; 10 | 11 | /** 12 | * Generates a querystring parameter containing the entityId 13 | * @author chasegawa@unicon.net 14 | * @author jgasper@unicon.net 15 | */ 16 | public class EntityIdParameterBuilder implements IParameterBuilder { 17 | private final Logger logger = LoggerFactory.getLogger(EntityIdParameterBuilder.class); 18 | 19 | @Override 20 | public String getParameterString(final HttpServletRequest request, final String authenticationKey) { 21 | return getParameterString(request, true); 22 | } 23 | 24 | public String getParameterString(final HttpServletRequest request, final boolean encode) { 25 | final String relayingPartyId = request.getAttribute(ExternalAuthentication.RELYING_PARTY_PARAM).toString(); 26 | 27 | String rpId = "error-encoding-rpid"; 28 | 29 | if (encode == true) { 30 | try { 31 | rpId = URLEncoder.encode(relayingPartyId, "UTF-8"); 32 | } catch (final UnsupportedEncodingException e) { 33 | logger.error("Error encoding the relying party id.", e); 34 | } 35 | } else { 36 | rpId = relayingPartyId; 37 | } 38 | 39 | return "&entityId=" + rpId; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return this.getClass().hashCode(); 45 | } 46 | 47 | @Override 48 | public boolean equals(final Object obj) { 49 | return obj instanceof EntityIdParameterBuilder; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/conf/ldap.properties: -------------------------------------------------------------------------------- 1 | # LDAP authentication configuration, see authn/ldap-authn-config.xml 2 | 3 | ## Authenticator strategy, either anonSearchAuthenticator, bindSearchAuthenticator, directAuthenticator, adAuthenticator 4 | #idp.authn.LDAP.authenticator = anonSearchAuthenticator 5 | 6 | ## Connection properties ## 7 | idp.authn.LDAP.ldapURL = ldap://ldap:389 8 | idp.authn.LDAP.useStartTLS = false 9 | idp.authn.LDAP.useSSL = false 10 | #idp.authn.LDAP.connectTimeout = 3000 11 | 12 | ## SSL configuration, either jvmTrust, certificateTrust, or keyStoreTrust 13 | #idp.authn.LDAP.sslConfig = certificateTrust 14 | ## If using certificateTrust above, set to the trusted certificate's path 15 | idp.authn.LDAP.trustCertificates = %{idp.home}/credentials/ldap-server.crt 16 | ## If using keyStoreTrust above, set to the truststore path 17 | idp.authn.LDAP.trustStore = %{idp.home}/credentials/ldap-server.truststore 18 | 19 | ## Return attributes during authentication 20 | ## NOTE: this is not used during attribute resolution; configure that directly in the 21 | ## attribute-resolver.xml configuration via a DataConnector's element 22 | idp.authn.LDAP.returnAttributes = cn,businessCategory,mail 23 | 24 | ## DN resolution properties ## 25 | 26 | # Search DN resolution, used by anonSearchAuthenticator, bindSearchAuthenticator 27 | # for AD: CN=Users,DC=example,DC=org 28 | idp.authn.LDAP.baseDN = ou=people,dc=example,dc=edu 29 | #idp.authn.LDAP.subtreeSearch = false 30 | idp.authn.LDAP.userFilter = (uid={user}) 31 | # bind search configuration 32 | # for AD: idp.authn.LDAP.bindDN=adminuser@domain.com 33 | idp.authn.LDAP.bindDN = cn=admin,dc=example,dc=edu 34 | idp.authn.LDAP.bindDNCredential = password 35 | 36 | # Format DN resolution, used by directAuthenticator, adAuthenticator 37 | # for AD use idp.authn.LDAP.dnFormat=%s@domain.com 38 | idp.authn.LDAP.dnFormat = uid=%s,ou=people,dc=example,dc=edu 39 | 40 | # LDAP attribute configuration, see attribute-resolver.xml 41 | idp.attribute.resolver.LDAP.ldapURL = %{idp.authn.LDAP.ldapURL} 42 | idp.attribute.resolver.LDAP.baseDN = %{idp.authn.LDAP.baseDN} 43 | idp.attribute.resolver.LDAP.bindDN = %{idp.authn.LDAP.bindDN} 44 | idp.attribute.resolver.LDAP.bindDNCredential = %{idp.authn.LDAP.bindDNCredential} 45 | idp.attribute.resolver.LDAP.useStartTLS = %{idp.authn.LDAP.useStartTLS:true} 46 | idp.attribute.resolver.LDAP.trustCertificates = %{idp.authn.LDAP.trustCertificates} 47 | idp.attribute.resolver.LDAP.searchFilter = (uid=$requestContext.principalName) 48 | 49 | # LDAP pool configuration, used for both authn and DN resolution 50 | #idp.pool.LDAP.minSize = 3 51 | #idp.pool.LDAP.maxSize = 10 52 | #idp.pool.LDAP.validateOnCheckout = false 53 | #idp.pool.LDAP.validatePeriodically = true 54 | #idp.pool.LDAP.validatePeriod = 300 55 | #idp.pool.LDAP.prunePeriod = 300 56 | #idp.pool.LDAP.idleTime = 600 57 | #idp.pool.LDAP.blockWaitTime = 3000 58 | #idp.pool.LDAP.failFastInitialize = false 59 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/authn/provider/extra/CasAuthnMethodParameterBuilder.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.authn.provider.extra; 2 | 3 | import net.shibboleth.idp.authn.ExternalAuthentication; 4 | import net.shibboleth.idp.authn.context.AuthenticationContext; 5 | import net.shibboleth.idp.authn.context.RequestedPrincipalContext; 6 | import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal; 7 | import org.opensaml.profile.context.ProfileRequestContext; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.BeansException; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import java.security.Principal; 16 | 17 | /** 18 | * Generates a querystring parameter containing the authn_method parameter. 19 | * 20 | * @author Misagh Moayyed 21 | */ 22 | public abstract class CasAuthnMethodParameterBuilder implements IParameterBuilder, ApplicationContextAware { 23 | private final Logger logger = LoggerFactory.getLogger(CasAuthnMethodParameterBuilder.class); 24 | protected ApplicationContext applicationContext; 25 | private static final String REFEDS = "https://refeds.org/profile/mfa"; 26 | 27 | @Override 28 | public String getParameterString(final HttpServletRequest request, final String authenticationKey) { 29 | try { 30 | final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(authenticationKey, request); 31 | final AuthenticationContext authnContext = prc.getSubcontext(AuthenticationContext.class, true); 32 | if (authnContext == null) { 33 | logger.debug("No authentication context is available"); 34 | return ""; 35 | } 36 | final RequestedPrincipalContext principalCtx = authnContext.getSubcontext(RequestedPrincipalContext.class, true); 37 | if (principalCtx == null || principalCtx.getRequestedPrincipals().isEmpty()) { 38 | logger.debug("No authentication method parameter is found in the request attributes"); 39 | return ""; 40 | } 41 | final Principal principal = new AuthnContextClassRefPrincipal(REFEDS); 42 | final Principal attribute = principalCtx.getRequestedPrincipals().stream().filter(p -> p.equals(principal)).findFirst().orElse(null); 43 | if (attribute == null) { 44 | return ""; 45 | } 46 | final String casMethod = getCasAuthenticationMethodFor(REFEDS); 47 | if (casMethod != null && !casMethod.isEmpty()) { 48 | return "&authn_method=" + casMethod; 49 | } 50 | return ""; 51 | }catch (final Exception e) { 52 | logger.error(e.getMessage(), e); 53 | return ""; 54 | } 55 | } 56 | 57 | protected abstract String getCasAuthenticationMethodFor(final String authnMethod); 58 | 59 | @Override 60 | public int hashCode() { 61 | return this.getClass().hashCode(); 62 | } 63 | 64 | @Override 65 | public boolean equals(final Object obj) { 66 | return obj instanceof CasAuthnMethodParameterBuilder; 67 | } 68 | 69 | @Override 70 | public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { 71 | this.applicationContext = applicationContext; 72 | } 73 | 74 | public static boolean isMultifactorRefedsProfile(final String authnMethod) { 75 | return REFEDS.equals(authnMethod); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/conf/metadata-providers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 66 | 67 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/externalauth/AuthenticatedNameTranslator.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.externalauth; 2 | 3 | import net.shibboleth.idp.attribute.IdPAttribute; 4 | import net.shibboleth.idp.attribute.StringAttributeValue; 5 | import net.shibboleth.idp.authn.ExternalAuthentication; 6 | import net.shibboleth.idp.authn.principal.IdPAttributePrincipal; 7 | import net.shibboleth.idp.authn.principal.UsernamePrincipal; 8 | import org.apache.commons.lang.builder.EqualsBuilder; 9 | import org.apache.commons.lang.builder.HashCodeBuilder; 10 | import org.jasig.cas.client.authentication.AttributePrincipal; 11 | import org.jasig.cas.client.validation.Assertion; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import javax.security.auth.Subject; 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.security.Principal; 19 | import java.util.ArrayList; 20 | import java.util.Collection; 21 | import java.util.Collections; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Set; 26 | 27 | /** 28 | * Simple translation of the principal name from the CAS assertion to the string value used by Shib 29 | * 30 | * @author chasegawa@unicon.net 31 | * @author jgasper@unicon.net 32 | */ 33 | public class AuthenticatedNameTranslator implements CasToShibTranslator { 34 | private final Logger logger = LoggerFactory.getLogger(AuthenticatedNameTranslator.class); 35 | 36 | @Override 37 | public void doTranslation(final HttpServletRequest request, final HttpServletResponse response, 38 | final Assertion assertion, final String authenticationKey) { 39 | if (assertion == null || assertion.getPrincipal() == null) { 40 | logger.error("No valid assertion or principal could be found to translate"); 41 | return; 42 | } 43 | final AttributePrincipal casPrincipal = assertion.getPrincipal(); 44 | logger.debug("principalName found and being passed on: {}", casPrincipal.getName()); 45 | 46 | // Pass authenticated principal back to IdP to finish its part of authentication request processing 47 | final Collection assertionAttributes = produceIdpAttributePrincipal(assertion.getAttributes()); 48 | final Collection principalAttributes = produceIdpAttributePrincipal(casPrincipal.getAttributes()); 49 | 50 | if (!assertionAttributes.isEmpty() || !principalAttributes.isEmpty()) { 51 | logger.debug("Found attributes from CAS. Processing..."); 52 | final Set principals = new HashSet<>(); 53 | 54 | principals.addAll(assertionAttributes); 55 | principals.addAll(principalAttributes); 56 | principals.add(new UsernamePrincipal(casPrincipal.getName())); 57 | 58 | request.setAttribute(ExternalAuthentication.SUBJECT_KEY, new Subject(false, principals, 59 | Collections.emptySet(), Collections.emptySet())); 60 | logger.info("Created an IdP subject instance with principals containing attributes for {} ", casPrincipal.getName()); 61 | 62 | } else { 63 | logger.debug("No attributes released from CAS. Creating an IdP principal for {}", casPrincipal.getName()); 64 | request.setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, casPrincipal.getName()); 65 | } 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return HashCodeBuilder.reflectionHashCode(this); 71 | } 72 | 73 | @Override 74 | public boolean equals(final Object that) { 75 | return EqualsBuilder.reflectionEquals(this, that); 76 | } 77 | 78 | 79 | private Collection produceIdpAttributePrincipal(final Map casAttributes) { 80 | final Set principals = new HashSet<>(); 81 | for (final Map.Entry entry : casAttributes.entrySet()) { 82 | final IdPAttribute attr = new IdPAttribute(entry.getKey()); 83 | 84 | final List attributeValues = new ArrayList<>(); 85 | if (entry.getValue() instanceof Collection) { 86 | for (final Object value : (Collection) entry.getValue()) { 87 | attributeValues.add(new StringAttributeValue(value.toString())); 88 | } 89 | } else { 90 | attributeValues.add(new StringAttributeValue(entry.getValue().toString())); 91 | } 92 | if (!attributeValues.isEmpty()) { 93 | attr.setValues(attributeValues); 94 | logger.debug("Added attribute {} with values {}", entry.getKey(), entry.getValue()); 95 | principals.add(new IdPAttributePrincipal(attr)); 96 | } else { 97 | logger.warn("Skipped attribute {} since it contains no values", entry.getKey()); 98 | } 99 | } 100 | return principals; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/metadata/sp-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 79943fee7865 34 | 35 | CN=79943fee7865 36 | MIIC6zCCAdOgAwIBAgIJAOy0nki3WAOVMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV 37 | BAMTDDc5OTQzZmVlNzg2NTAeFw0xNTEyMTEwMzAwNDJaFw0yNTEyMDgwMzAwNDJa 38 | MBcxFTATBgNVBAMTDDc5OTQzZmVlNzg2NTCCASIwDQYJKoZIhvcNAQEBBQADggEP 39 | ADCCAQoCggEBALPBzAz0DTn+j2YsQKfqWI+m08lP5UzwVsE9ZKzLqO3PRHZqiOBm 40 | EaFmRrYCZCAOcJ0TXcxPGtNSo8HC4uw5/Y5lJGuI3jN7X7KB1VUQDpUSwfgOqtro 41 | uDoVRKrsaYZTnlNV8KbZ0WQz5s4Uw6CxKRB9RZ5iQMP1fuxc8B6GSOb3x69MiY6c 42 | 1jlgVAc6rV4zGfpafacxOLM8qcYhY8u3TiSd0H+oiGEqi1mFLK8yp6FKzX8OUkQf 43 | We49YHz6wBxFOe+/p+7ziym1rBs/lGfenEo8ziCIMmjnoo257fz00bcz9rFl1rTx 44 | KLFfgy72xTlG72l6u+pB9VqK3YNJS52Ns5UCAwEAAaM6MDgwFwYDVR0RBBAwDoIM 45 | Nzk5NDNmZWU3ODY1MB0GA1UdDgQWBBRiDMNPjiAMC50WWubI3PMjP45S/DANBgkq 46 | hkiG9w0BAQUFAAOCAQEAYZM/iWgC93vAq0d98egEzvESKodxHffkDOagd4Kxt/S0 47 | AAHsVQCmAK/9kmRhsWzR3f1KIw98q4EX7nH/K68BFrerUvaL5+fEGE9W6Ki6QdW8 48 | bM17GQkLyRDKZzGPm/hsaG1Oxru2kDf7qSvv59aRZlZ8skrDEnx8+dZ8JKC02ZDU 49 | ClC+xWl1UPfO2BL4tJei/siSymGpiRqznQ2JMoTFu5CUUpoxyCVz1bl9lCVceoJ9 50 | FaL38knS0p5DnXcm+I8wqNEVGLDPbDalBQryhJT9fIMm1/B85gB3AWAvcu9PPfHK 51 | lQQUhxyEXTBJx3luLlpIjoloFKIute9K7pE5qAENjg== 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/test/docker/cas_server/cas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | org.apereo.cas 7 | cas-overlay 8 | war 9 | 1.0 10 | 11 | 12 | 13 | 14 | com.rimerosolutions.maven.plugins 15 | wrapper-maven-plugin 16 | 0.0.4 17 | 18 | true 19 | MD5 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-maven-plugin 25 | ${springboot.version} 26 | 27 | org.springframework.boot.loader.WarLauncher 28 | true 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-war-plugin 34 | 2.6 35 | 36 | cas 37 | false 38 | false 39 | 40 | false 41 | ${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp/META-INF/MANIFEST.MF 42 | 43 | 44 | 45 | 46 | org.apereo.cas 47 | cas-server-webapp 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-compiler-plugin 55 | 3.3 56 | 57 | 58 | cas 59 | 60 | 61 | 62 | 63 | org.apereo.cas 64 | cas-server-webapp 65 | ${cas.version} 66 | war 67 | runtime 68 | 69 | 70 | org.apereo.cas 71 | cas-server-support-ldap 72 | ${cas.version} 73 | 74 | 75 | org.apereo.cas 76 | cas-server-support-json-service-registry 77 | ${cas.version} 78 | 79 | 80 | 81 | 82 | 5.0.5 83 | 1.4.2.RELEASE 84 | 1.8 85 | 1.8 86 | UTF-8 87 | 88 | 89 | 90 | 91 | sonatype-releases 92 | http://oss.sonatype.org/content/repositories/releases/ 93 | 94 | false 95 | 96 | 97 | true 98 | 99 | 100 | 101 | sonatype-snapshots 102 | https://oss.sonatype.org/content/repositories/snapshots/ 103 | 104 | true 105 | 106 | 107 | false 108 | 109 | 110 | 111 | shibboleth-releases 112 | https://build.shibboleth.net/nexus/content/repositories/releases 113 | 114 | 115 | spring-milestones 116 | https://repo.spring.io/milestone 117 | 118 | 119 | 120 | 121 | 122 | 123 | false 124 | 125 | pgp 126 | 127 | 128 | 129 | com.github.s4u.plugins 130 | pgpverify-maven-plugin 131 | 1.1.0 132 | 133 | 134 | 135 | check 136 | 137 | 138 | 139 | 140 | hkp://pool.sks-keyservers.net 141 | ${settings.localRepository}/pgpkeys-cache 142 | test 143 | true 144 | false 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/externalauth/CasDuoSecurityRefedsAuthnMethodTranslator.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.externalauth; 2 | 3 | import net.shibboleth.idp.authn.ExternalAuthentication; 4 | import net.shibboleth.idp.authn.context.AuthenticationContext; 5 | import net.shibboleth.idp.authn.context.RequestedPrincipalContext; 6 | import net.shibboleth.idp.authn.principal.PrincipalEvalPredicate; 7 | import net.shibboleth.idp.authn.principal.PrincipalEvalPredicateFactory; 8 | import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent; 9 | import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal; 10 | import org.jasig.cas.client.validation.Assertion; 11 | import org.opensaml.profile.context.ProfileRequestContext; 12 | import org.opensaml.saml.saml2.core.AuthnContext; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.context.EnvironmentAware; 16 | import org.springframework.core.env.Environment; 17 | 18 | import javax.annotation.Nonnull; 19 | import javax.annotation.Nullable; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import java.security.Principal; 23 | import java.util.ArrayList; 24 | import java.util.HashSet; 25 | import java.util.List; 26 | import java.util.Set; 27 | 28 | public class CasDuoSecurityRefedsAuthnMethodTranslator implements CasToShibTranslator, EnvironmentAware { 29 | private final Logger logger = LoggerFactory.getLogger(CasDuoSecurityRefedsAuthnMethodTranslator.class); 30 | 31 | private static final String REFEDS = "https://refeds.org/profile/mfa"; 32 | 33 | private Environment environment; 34 | 35 | @Override 36 | public void setEnvironment(final Environment environment) { 37 | this.environment = environment; 38 | } 39 | 40 | @Override 41 | public void doTranslation(final HttpServletRequest request, final HttpServletResponse response, final Assertion assertion, final String authenticationKey) throws Exception { 42 | 43 | final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(authenticationKey, request); 44 | final AuthenticationContext authnContext = prc.getSubcontext(AuthenticationContext.class, true); 45 | if (authnContext == null) { 46 | logger.debug("No authentication context is available"); 47 | return; 48 | } 49 | final RequestedPrincipalContext principalCtx = authnContext.getSubcontext(RequestedPrincipalContext.class, true); 50 | if (principalCtx == null || principalCtx.getRequestedPrincipals().isEmpty()) { 51 | logger.debug("No requested principal context is available in the authentication context; Overriding class to {}", AuthnContext.PPT_AUTHN_CTX); 52 | overrideAuthnContextClass(AuthnContext.PPT_AUTHN_CTX, request, authenticationKey); 53 | return; 54 | } 55 | 56 | final Principal principal = new AuthnContextClassRefPrincipal(REFEDS); 57 | final Principal attribute = principalCtx.getRequestedPrincipals().stream().filter(p -> p.equals(principal)).findFirst().orElse(null); 58 | if (attribute == null) { 59 | logger.debug("No authn context class ref principal is found in the requested principals; overriding to {}", AuthnContext.PPT_AUTHN_CTX); 60 | overrideAuthnContextClass(AuthnContext.PPT_AUTHN_CTX, request, authenticationKey); 61 | return; 62 | } 63 | final String authnMethod = attribute.getName(); 64 | logger.debug("Requested authn method provided by IdP is {}", authnMethod); 65 | if (!assertion.getPrincipal().getAttributes().containsKey("authnContextClass")) { 66 | logger.debug("No authentication context class is provided by CAS; Overriding context class to {}", AuthnContext.PPT_AUTHN_CTX); 67 | overrideAuthnContextClass(AuthnContext.PPT_AUTHN_CTX, request, authenticationKey); 68 | return; 69 | } 70 | 71 | final Object clazz = assertion.getPrincipal().getAttributes().get("authnContextClass"); 72 | logger.debug("Located asserted authentication context class [{}]", clazz); 73 | 74 | if (clazz.equals("mfa-duo")) { 75 | overrideAuthnContextClass(REFEDS, request, authenticationKey); 76 | logger.info("Validation payload successfully asserts the authentication context class for mfa-duo; Context class is set to {}", REFEDS); 77 | return; 78 | } 79 | logger.debug("Authentication context class [{}] provided by CAS is not one by Duo Security. " 80 | + "The requested authentication method to be used shall be {} and is left unmodified", clazz, authnMethod); 81 | overrideAuthnContextClass(clazz.toString(), request, authenticationKey); 82 | } 83 | 84 | private void overrideAuthnContextClass(final String clazz, final HttpServletRequest request, final String authenticationKey) throws Exception { 85 | final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(authenticationKey, request); 86 | final AuthenticationContext authnContext = prc.getSubcontext(AuthenticationContext.class, true); 87 | if (authnContext == null) { 88 | throw new IllegalArgumentException("No authentication method parameter is found in the request attributes"); 89 | } 90 | final RequestedPrincipalContext principalCtx = authnContext.getSubcontext(RequestedPrincipalContext.class, true); 91 | logger.info("Overriding the principal authn context class ref to {}", clazz); 92 | if (principalCtx != null) { 93 | final List principals = new ArrayList<>(); 94 | final Principal principal = new AuthnContextClassRefPrincipal(clazz); 95 | principals.add(principal); 96 | principalCtx.setRequestedPrincipals(principals); 97 | principalCtx.setOperator("exact"); 98 | principalCtx.setMatchingPrincipal(principal); 99 | 100 | principalCtx.getPrincipalEvalPredicateFactoryRegistry().register(AuthnContextClassRefPrincipal.class, "exact", new PrincipalEvalPredicateFactory() { 101 | @Nonnull 102 | @Override 103 | public PrincipalEvalPredicate getPredicate(@Nonnull final Principal candidate) { 104 | return new PrincipalEvalPredicate() { 105 | 106 | @Override 107 | public Principal getMatchingPrincipal() { 108 | return principal; 109 | } 110 | 111 | @Override 112 | public boolean apply(@Nullable final PrincipalSupportingComponent input) { 113 | final Set supported = input != null 114 | ? input.getSupportedPrincipals(principal.getClass()) 115 | : new HashSet(); 116 | return supported.stream().anyMatch(p -> principal.equals(p)); 117 | } 118 | }; 119 | } 120 | }); 121 | 122 | logger.info("The final requested authn context class ref principals are {}", principals); 123 | } else { 124 | logger.error("No requested principal context class is available"); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/test/docker/cas_server/cas/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | wdir=$(cd "$wdir/.."; pwd) 204 | if [ -d "$wdir"/.mvn ] ; then 205 | basedir=$wdir 206 | break 207 | fi 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER="org.apache.maven.wrapper.MavenWrapperMain" 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 232 | -classpath \ 233 | "$MAVEN_PROJECTBASEDIR/maven/maven-wrapper.jar" \ 234 | ${WRAPPER_LAUNCHER} "$@" 235 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/conf/authn/general-authn.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 28 | 29 | 30 | 31 | 35 | 36 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 53 | 55 | 56 | 57 | 58 | 59 | 61 | 62 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 73 | 75 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 86 | 88 | 90 | 91 | 92 | 93 | 94 | 97 | 98 | 101 | 107 | 108 | 109 | 111 | 113 | 114 | 115 | 116 | 117 | 120 | 126 | 127 | 128 | 130 | 132 | 134 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 150 | 151 | 152 | 153 | 154 | 156 | 157 | 1 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NOTE Development has moved. Please navigate to for future version and updates! 2 | 3 | ## A Shibboleth IdP v3.X plugin for authentication via an external CAS Server 4 | 5 | 6 | > A Shibboleth IdP v4.X plugin can be found at 7 | 8 | This is a Shibboleth IdP external authentication plugin that delegates the authentication to an external 9 | Central Authentication Server. The biggest advantage of using this component over the plain 10 | `REMOTE_USER` header solution provided by Shibboleth is the ability to utilize a full range 11 | of native CAS protocol features such as `renew` and `gateway`, plus the ability to share with CAS the 12 | EntityID of the relying application. 13 | 14 | The plugin takes advantage of and extends the Shibboleth IdP's external authentication flow, and consists of a number of JAR artifacts that bridge the gap between Shibboleth and CAS. 15 | 16 | Maintenance Status 17 | ------------------------------------------------------------- 18 | 19 | Maintenance of this project is sponsored by Unicon's [Open Source Support program](https://unicon.net/support). Professional support/integration assistance for this module is available. For more information, visit . 20 | 21 | Also, please do note that the Shibboleth IdP v3 has support for the CAS protocol and Apereo CAS server v5+ also has support for the SAML2 protocol. Unless justified otherwise, a better approach long-term would be to consolidate down to one platform removing the need to deploy and configure this plugin. 22 | 23 | Build Status 24 | ------------------------------------------------------------- 25 | [![Build Status](https://travis-ci.org/Unicon/shib-cas-authn3.svg?branch=master)](https://travis-ci.org/Unicon/shib-cas-authn3) 26 | 27 | Software Requirements 28 | ------------------------------------------------------------- 29 | 30 | This minimum supported version of Shibboleth Identity Provider is `3.3.0`. As of version `3.3.0`, the minimum supported version of Shibboleth Identity Provider is `3.4.6` which contains a fix for *Denial of service via External authentication flows*. See [this link](https://wiki.shibboleth.net/confluence/display/IDP30/SecurityAdvisories) for more details. 31 | 32 | > A Shibboleth IdP v2.X plugin can be found at . 33 | 34 | Installation 35 | --------------------------------------------------------------- 36 | 37 | #### Overview 38 | 39 | - Download and extract the "latest release" zip or tar [from releases](https://github.com/Unicon/shib-cas-authn3/releases). 40 | - Copy the no-conversation-state.jsp file to your `IDP_HOME/edit-webapp` 41 | - Copy two included jar files (`cas-client-core-x.x.x.jar` and `shib-casuathenticator-x.x.x.jar`) into the `IDP_HOME/edit-webapp/WEB-INF/lib`. 42 | - Update the IdP's `web.xml`. 43 | - Update the IdP's `idp.properties` file. 44 | - Rebuild the war file. 45 | 46 | **NOTE:** You should **ALWAYS** refers to the `README.md` file that is [packaged with the release](https://github.com/Unicon/shib-cas-authn3/releases) for instructions. 47 | 48 | 49 | #### Update the IdP's `web.xml` 50 | 51 | Add the ShibCas Auth Servlet entry in `IDP_HOME/edit-webapp/WEB-INF/web.xml` (Copy from `IDP_HOME/webapp/WEB-INF/web.xml`, if necessary.) 52 | 53 | Example snippet `web.xml`: 54 | 55 | ```xml 56 | ... 57 | 58 | 59 | ShibCas Auth Servlet 60 | net.unicon.idp.externalauth.ShibcasAuthServlet 61 | 2 62 | 63 | 64 | ShibCas Auth Servlet 65 | /Authn/External/* 66 | 67 | ... 68 | ``` 69 | 70 | #### Update the IdP's idp.properties file 71 | 72 | 1. Set the `idp.authn.flows` to `External`. Or, for advance cases, add `External` to the list. 73 | 1. Add the additional properties. 74 | 75 | ```properties 76 | ... 77 | # Regular expression matching login flows to enable, e.g. IPAddress|Password 78 | #idp.authn.flows = Password 79 | idp.authn.flows = External 80 | 81 | # CAS Client properties (usage loosely matches that of the Java CAS Client) 82 | ## CAS Server Properties 83 | shibcas.casServerUrlPrefix = https://cassserver.example.edu/cas 84 | shibcas.casServerLoginUrl = ${shibcas.casServerUrlPrefix}/login 85 | 86 | ## Shibboleth Server Properties 87 | shibcas.serverName = https://shibserver.example.edu 88 | 89 | # By default you always get the AuthenticatedNameTranslator, add additional code to cover your custom needs. 90 | # Takes a comma separated list of fully qualified class names 91 | # shibcas.casToShibTranslators = com.your.institution.MyCustomNamedTranslatorClass 92 | # shibcas.parameterBuilders = com.your.institution.MyParameterBuilderClass 93 | 94 | # Specify CAS validator to use - either 'cas10', 'cas20' or 'cas30' (default) 95 | # shibcas.ticketValidatorName = cas30 96 | 97 | 98 | # Specify if the Relying Party/Service Provider entityId should be appended as a separate entityId query string parameter 99 | # or embedded in the "service" querystring parameter - `append` (default) or `embed` 100 | # shibcas.entityIdLocation = append 101 | ... 102 | ``` 103 | 104 | 105 | #### Rebuild the war file 106 | 107 | From the `IDP_HOME/bin` directory, run `./build.sh` or `build.bat` to rebuild the `idp.war`. Redeploy if necessary. 108 | 109 | #### CAS Service Registry 110 | By setting `shibcas.entityIdLocation=embed`, shib-cas-authn will embed the entityId in the service string so that CAS Server 111 | can use the entityId when evaluating a service registry entry match. Using serviceIds of something like: 112 | `https://shibserver.example.edu/idp/Authn/ExtCas\?conversation=[a-z0-9]*&entityId=http://testsp.school.edu/sp` 113 | or 114 | `https://shibserver.example.edu/idp/Authn/ExtCas\?conversation=[a-z0-9]*&entityId=http://test.unicon.net/sp` 115 | will match as two different entries in the service registry which will allow as CAS admin to enable MFA or use access strategies on an SP by SP basis. 116 | 117 | Handling REFEDS MFA Profile 118 | --------------------------------------------------------------- 119 | 120 | Note: This feature is only available, starting with version `3.2.4`. 121 | 122 | The plugin has native support for [REFEDS MFA profile](https://refeds.org/profile/mfa). The requested authentication context class that is `https://refeds.org/profile/mfa` 123 | is passed along from the Shibboleth IdP over to this plugin and is then translated to a multifactor authentication strategy supported by and configured CAS (i.e. Duo Security). 124 | The CAS server is notified of the required authentication method via a special `authn_method` parameter by default. Once a service ticket is issued and plugin begins to 125 | validate the service ticket, it will attempt to ensure that the CAS-produced validation payload contains and can successfully assert the required/requested 126 | authentication context class. 127 | 128 | The supported multifactor authentication providers are listed below: 129 | 130 | - Duo Security (Requesting `authn_method=mfa-duo` and expecting validation payload attribute `authnContextClass=mfa-duo`) 131 | 132 | #### Configuration 133 | 134 | In the `idp.properties` file, ensure the following settings are set: 135 | 136 | ```properties 137 | shibcas.casToShibTranslators = net.unicon.idp.externalauth.CasDuoSecurityRefedsAuthnMethodTranslator 138 | shibcas.parameterBuilders = net.unicon.idp.authn.provider.extra.CasMultifactorRefedsToDuoSecurityAuthnMethodParameterBuilder 139 | ``` 140 | 141 | You also need to ensure the `authn/External` flow is able to accept the requested principal in the IdP's `general-authn.xml` file, that is `https://refeds.org/profile/mfa`. 142 | 143 | ```xml 144 | 148 | 149 | 150 | 152 | 154 | 155 | 156 | 157 | ``` 158 | 159 | Release Notes 160 | ------------------------------------------------------------- 161 | See [here](https://github.com/Unicon/shib-cas-authn3/releases/). 162 | 163 | Developer Notes 164 | ------------------------------------------------------------- 165 | The project distributables can be built using `./gradlew clean build`. The artifacts will be in `build/distributions`. 166 | 167 | This project includes a Docker environment to assist with development/testing. 168 | 169 | To build and execute: `./gradlew clean; ./gradlew up` 170 | Then browse to: `https://idptestbed/idp/profile/SAML2/Unsolicited/SSO?providerId=https://sp.idptestbed/shibboleth` 171 | 172 | > You'll need a `hosts` file entry that points `idptestbed` to your Docker server's IP address. 173 | 174 | The IdP only has a session of 1 minute (to test expired session/conversation key issues), so login into CAS Server quickly. 175 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/conf/idp.properties: -------------------------------------------------------------------------------- 1 | # Load any additional property resources from a comma-delimited list 2 | idp.additionalProperties= /conf/ldap.properties, /conf/saml-nameid.properties, /conf/services.properties 3 | 4 | # Set the entityID of the IdP 5 | idp.entityID= https://idptestbed/idp/shibboleth 6 | 7 | # Set the scope used in the attribute resolver for scoped attributes 8 | idp.scope= example.org 9 | 10 | # General cookie properties (maxAge only applies to persistent cookies) 11 | #idp.cookie.secure = false 12 | #idp.cookie.httpOnly = true 13 | #idp.cookie.domain = 14 | #idp.cookie.path = 15 | #idp.cookie.maxAge = 31536000 16 | 17 | # Set the location of user-supplied web flow definitions 18 | #idp.webflows = %{idp.home}/flows 19 | 20 | # Set the location of Velocity view templates 21 | #idp.views = %{idp.home}/views 22 | 23 | # Settings for internal AES encryption key 24 | #idp.sealer.storeType = JCEKS 25 | #idp.sealer.updateInterval = PT15M 26 | #idp.sealer.aliasBase = secret 27 | idp.sealer.storeResource= %{idp.home}/credentials/sealer.jks 28 | idp.sealer.versionResource= %{idp.home}/credentials/sealer.kver 29 | idp.sealer.storePassword= password 30 | idp.sealer.keyPassword= password 31 | 32 | # Settings for public/private signing and encryption key(s) 33 | # During decryption key rollover, point the ".2" properties at a second 34 | # keypair, uncomment in credentials.xml, then publish it in your metadata. 35 | idp.signing.key= %{idp.home}/credentials/idp-signing.key 36 | idp.signing.cert= %{idp.home}/credentials/idp-signing.crt 37 | idp.encryption.key= %{idp.home}/credentials/idp-encryption.key 38 | idp.encryption.cert= %{idp.home}/credentials/idp-encryption.crt 39 | #idp.encryption.key.2 = %{idp.home}/credentials/idp-encryption-old.key 40 | #idp.encryption.cert.2 = %{idp.home}/credentials/idp-encryption-old.crt 41 | 42 | # Sets the bean ID to use as a default security configuration set 43 | #idp.security.config = shibboleth.DefaultSecurityConfiguration 44 | 45 | # To default to SHA-1, set to shibboleth.SigningConfiguration.SHA1 46 | #idp.signing.config = shibboleth.SigningConfiguration.SHA256 47 | 48 | # Configures trust evaluation of keys used by services at runtime 49 | # Defaults to supporting both explicit key and PKIX using SAML metadata. 50 | #idp.trust.signatures = shibboleth.ChainingSignatureTrustEngine 51 | # To pick only one set to one of: 52 | # shibboleth.ExplicitKeySignatureTrustEngine, shibboleth.PKIXSignatureTrustEngine 53 | #idp.trust.certificates = shibboleth.ChainingX509TrustEngine 54 | # To pick only one set to one of: 55 | # shibboleth.ExplicitKeyX509TrustEngine, shibboleth.PKIXX509TrustEngine 56 | 57 | # If true, encryption will happen whenever a key to use can be located, but 58 | # failure to encrypt won't result in request failure. 59 | #idp.encryption.optional = false 60 | 61 | # Configuration of client- and server-side storage plugins 62 | #idp.storage.cleanupInterval = PT10M 63 | #idp.storage.htmlLocalStorage = false 64 | 65 | # Set to true to expose more detailed errors in responses to SPs 66 | #idp.errors.detailed = false 67 | # Set to false to skip signing of SAML response messages that signal errors 68 | #idp.errors.signed = true 69 | # Name of bean containing a list of Java exception classes to ignore 70 | #idp.errors.excludedExceptions = ExceptionClassListBean 71 | # Name of bean containing a property set mapping exception names to views 72 | #idp.errors.exceptionMappings = ExceptionToViewPropertyBean 73 | # Set if a different default view name for events and exceptions is needed 74 | #idp.errors.defaultView = error 75 | 76 | # Set to false to disable the IdP session layer 77 | #idp.session.enabled = true 78 | 79 | # Set to "shibboleth.StorageService" for server-side storage of user sessions 80 | #idp.session.StorageService = shibboleth.ClientSessionStorageService 81 | idp.session.StorageService = shibboleth.StorageService 82 | 83 | # Size of session IDs 84 | #idp.session.idSize = 32 85 | # Bind sessions to IP addresses 86 | #idp.session.consistentAddress = true 87 | # Inactivity timeout 88 | #idp.session.timeout = PT60M 89 | # Extra time to store sessions for logout 90 | #idp.session.slop = PT0S 91 | # Tolerate storage-related errors 92 | #idp.session.maskStorageFailure = false 93 | # Track information about SPs logged into 94 | #idp.session.trackSPSessions = false 95 | # Support lookup by SP for SAML logout 96 | #idp.session.secondaryServiceIndex = false 97 | # Length of time to track SP sessions 98 | #idp.session.defaultSPlifetime = PT2H 99 | 100 | # Regular expression matching login flows to enable, e.g. IPAddress|Password 101 | idp.authn.flows= Shibcas 102 | 103 | # Regular expression of forced "initial" methods when no session exists, 104 | # usually in conjunction with the idp.authn.resolveAttribute property below. 105 | #idp.authn.flows.initial = Password 106 | 107 | # Set to an attribute ID to resolve prior to selecting authentication flows; 108 | # its values are used to filter the flows to allow. 109 | #idp.authn.resolveAttribute = eduPersonAssurance 110 | 111 | # Default lifetime and timeout of various authentication methods 112 | #idp.authn.defaultLifetime = PT60M 113 | #idp.authn.defaultTimeout = PT30M 114 | 115 | # Whether to prioritize "active" results when an SP requests more than 116 | # one possible matching login method (V2 behavior was to favor them) 117 | #idp.authn.favorSSO = true 118 | 119 | # Whether to fail requests when a user identity after authentication 120 | # doesn't match the identity in a pre-existing session. 121 | #idp.authn.identitySwitchIsError = false 122 | 123 | # Set to "shibboleth.StorageService" or custom bean for alternate storage of consent 124 | #idp.consent.StorageService = shibboleth.ClientPersistentStorageService 125 | 126 | # Set to "shibboleth.consent.AttributeConsentStorageKey" to use an attribute 127 | # to key user consent storage records (and set the attribute name) 128 | #idp.consent.userStorageKey = shibboleth.consent.PrincipalConsentStorageKey 129 | #idp.consent.userStorageKeyAttribute = uid 130 | 131 | # Flags controlling how built-in attribute consent feature operates 132 | #idp.consent.allowDoNotRemember = true 133 | #idp.consent.allowGlobal = true 134 | #idp.consent.allowPerAttribute = false 135 | 136 | # Whether attribute values and terms of use text are compared 137 | #idp.consent.compareValues = false 138 | # Maximum number of consent records for space-limited storage (e.g. cookies) 139 | #idp.consent.maxStoredRecords = 10 140 | # Maximum number of consent records for larger/server-side storage (0 = no limit) 141 | #idp.consent.expandedMaxStoredRecords = 0 142 | 143 | # Time in milliseconds to expire consent storage records. 144 | #idp.consent.storageRecordLifetime = P1Y 145 | 146 | # Whether to lookup metadata, etc. for every SP involved in a logout 147 | # for use by user interface logic; adds overhead so off by default. 148 | #idp.logout.elaboration = false 149 | 150 | # Whether to require logout requests be signed/authenticated. 151 | #idp.logout.authenticated = true 152 | 153 | # Message freshness and replay cache tuning 154 | #idp.policy.messageLifetime = PT3M 155 | #idp.policy.clockSkew = PT3M 156 | 157 | # Set to custom bean for alternate storage of replay cache 158 | #idp.replayCache.StorageService = shibboleth.StorageService 159 | 160 | # Toggles whether to allow outbound messages via SAML artifact 161 | #idp.artifact.enabled = true 162 | # Suppresses typical signing/encryption when artifact binding used 163 | #idp.artifact.secureChannel = true 164 | # May differ to direct SAML 2 artifact lookups to specific server nodes 165 | #idp.artifact.endpointIndex = 2 166 | # Set to custom bean for alternate storage of artifact map state 167 | #idp.artifact.StorageService = shibboleth.StorageService 168 | 169 | # Comma-delimited languages to use if not match can be found with the 170 | # browser-supported languages, defaults to an empty list. 171 | idp.ui.fallbackLanguages= en,fr,de 172 | 173 | # Storage service used by CAS protocol 174 | # Defaults to shibboleth.StorageService (in-memory) 175 | # MUST be server-side storage (e.g. in-memory, memcached, database) 176 | # NOTE that idp.session.StorageService requires server-side storage 177 | # when CAS protocol is enabled 178 | idp.cas.StorageService=shibboleth.StorageService 179 | 180 | # CAS service registry implementation class 181 | #idp.cas.serviceRegistryClass=net.shibboleth.idp.cas.service.PatternServiceRegistry 182 | 183 | # Profile flows in which the ProfileRequestContext should be exposed 184 | # in servlet request under the key "opensamlProfileRequestContext" 185 | #idp.profile.exposeProfileRequestContextInServletRequest = SAML2/POST/SSO,SAML2/Redirect/SSO 186 | 187 | # F-TICKS auditing - set salt to include hashed username 188 | #idp.fticks.federation=MyFederation 189 | #idp.fticks.algorithm=SHA-256 190 | #idp.fticks.salt=somethingsecret 191 | 192 | # CAS Client properties (usage loosely matches that of the Java CAS Client) 193 | ## CAS Server Properties 194 | shibcas.casServerUrlPrefix = http://cas_server:8443/cas 195 | shibcas.casServerLoginUrl = http://localhost:8443/cas/login 196 | 197 | ## Shibboleth Server Properties 198 | shibcas.serverName = https://idptestbed 199 | 200 | # By default you always get the AuthenticatedNameTranslator, add additional code to cover your custom needs. 201 | # Takes a comma separated list of fully qualified class names 202 | # shibcas.casToShibTranslators = com.your.institution.MyCustomNamedTranslatorClass 203 | # shibcas.parameterBuilders = com.your.institution.MyParameterBuilderClass 204 | 205 | # Specify CAS validator to use - either 'cas20' or 'cas30' (default) 206 | # shibcas.ticketValidatorName = cas30 -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Shibboleth Identity Provider 6 | 7 | 9 | 10 | contextConfigLocation 11 | classpath*:/META-INF/net.shibboleth.idp/preconfig.xml,${idp.home}/system/conf/global-system.xml,classpath*:/META-INF/net.shibboleth.idp/config.xml,classpath*:/META-INF/net.shibboleth.idp/postconfig.xml 12 | 13 | 14 | 15 | contextClass 16 | net.shibboleth.ext.spring.context.DelimiterAwareApplicationContext 17 | 18 | 19 | 20 | contextInitializerClasses 21 | net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer 22 | 23 | 24 | 25 | 26 | org.springframework.web.context.ContextLoaderListener 27 | 28 | 29 | 30 | 31 | 32 | CharacterEncodingFilter 33 | org.springframework.web.filter.CharacterEncodingFilter 34 | 35 | encoding 36 | UTF-8 37 | 38 | 39 | forceEncoding 40 | true 41 | 42 | 43 | 44 | 45 | CookieBufferingFilter 46 | net.shibboleth.utilities.java.support.net.CookieBufferingFilter 47 | 48 | 49 | 50 | RequestResponseContextFilter 51 | net.shibboleth.utilities.java.support.net.RequestResponseContextFilter 52 | 53 | 54 | 55 | SLF4JMDCServletFilter 56 | net.shibboleth.idp.log.SLF4JMDCServletFilter 57 | 58 | 59 | CookieBufferingFilter 60 | /profile/Logout 61 | /profile/Shibboleth/SSO 62 | /profile/SAML2/Unsolicited/SSO 63 | /profile/SAML2/Redirect/SSO 64 | /profile/SAML2/POST/SSO 65 | /profile/SAML2/POST-SimpleSign/SSO 66 | /profile/SAML2/Redirect/SLO 67 | /profile/SAML2/POST/SLO 68 | /profile/SAML2/POST-SimpleSign/SLO 69 | /profile/cas/login 70 | 71 | 72 | CharacterEncodingFilter 73 | /* 74 | 75 | 76 | RequestResponseContextFilter 77 | /* 78 | 79 | 80 | SLF4JMDCServletFilter 81 | /* 82 | 83 | 84 | 85 | 86 | idp 87 | org.springframework.web.servlet.DispatcherServlet 88 | 89 | contextConfigLocation 90 | ${idp.home}/system/conf/mvc-beans.xml, ${idp.home}/system/conf/webflow-config.xml 91 | 92 | 93 | contextClass 94 | net.shibboleth.ext.spring.context.DelimiterAwareApplicationContext 95 | 96 | 1 97 | 98 | 99 | idp 100 | /status 101 | /profile/* 102 | 103 | 104 | 105 | 106 | RemoteUserAuthHandler 107 | net.shibboleth.idp.authn.impl.RemoteUserAuthServlet 108 | 2 109 | 110 | 111 | RemoteUserAuthHandler 112 | /Authn/RemoteUser 113 | 114 | 115 | 116 | 117 | X509AuthHandler 118 | net.shibboleth.idp.authn.impl.X509AuthServlet 119 | 3 120 | 121 | 122 | X509AuthHandler 123 | /Authn/X509 124 | 125 | 126 | 127 | 128 | shibboleth_jsp 129 | /WEB-INF/jsp/metadata.jsp 130 | 131 | 132 | shibboleth_jsp 133 | /shibboleth 134 | 135 | 136 | 137 | 1 138 | 139 | 140 | 141 | 142 | net.shibboleth.idp.authn.ExternalAuthenticationException 143 | /profile/RaiseError 144 | 145 | 146 | 147 | 148 | 149 | Non-API Content 150 | /* 151 | PUT 152 | PATCH 153 | DELETE 154 | OPTIONS 155 | TRACE 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | Administrative APIs 164 | /profile/admin/* 165 | 166 | 167 | 168 | 169 | 175 | 192 | 193 | 194 | 200 | 201 | 208 | 218 | 219 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/test/docker/shib_idp/shibboleth-idp/metadata/idp-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | example.org 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | MIIDEzCCAfugAwIBAgIUS9SuTXwsFVVG+LjOEAbLqqT/el0wDQYJKoZIhvcNAQEL 30 | BQAwFTETMBEGA1UEAwwKaWRwdGVzdGJlZDAeFw0xNTEyMTEwMjIwMjZaFw0zNTEy 31 | MTEwMjIwMjZaMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwggEiMA0GCSqGSIb3DQEB 32 | AQUAA4IBDwAwggEKAoIBAQCMAoDHx8xCIfv/6QKqt9mcHYmEJ8y2dKprUbpdcOjH 33 | YvNPIl/lHPsUyrb+Nc+q2CDeiWjVk1mWYq0UpIwpBMuw1H6+oOqr4VQRi65pin0M 34 | SfE0MWIaFo5FPvpvoptkHD4gvREbm4swyXGMczcMRfqgalFXhUD2wz8W3XAM5Cq2 35 | 03XeJbj6TwjvKatG5XPdeUe2FBGuOO2q54L1hcIGnLMCQrg7D31lR13PJbjnJ0No 36 | 5C3k8TPuny6vJsBC03GNLNKfmrKVTdzr3VKp1uay1G3DL9314fgmbl8HA5iRQmy+ 37 | XInUU6/8NXZSF59p3ITAOvZQeZsbJjg5gGDip5OZo9YlAgMBAAGjWzBZMB0GA1Ud 38 | DgQWBBRPlM4VkKZ0U4ec9GrIhFQl0hNbLDA4BgNVHREEMTAvggppZHB0ZXN0YmVk 39 | hiFodHRwczovL2lkcHRlc3RiZWQvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQEL 40 | BQADggEBAIZ0a1ov3my3ljJG588I/PHx+TxAWONWmpKbO9c/qI3Drxk4oRIffiac 41 | ANxdvtabgIzrlk5gMMisD7oyqHJiWgKv5Bgctd8w3IS3lLl7wHX65mTKQRXniG98 42 | NIjkvfrhe2eeJxecOqnDI8GOhIGCIqZUn8ShdM/yHjhQ2Mh0Hj3U0LlKvnmfGSQl 43 | j0viGwbFCaNaIP3zc5UmCrdE5h8sWL3Fu7ILKM9RyFa2ILHrJScV9t623IcHffHP 44 | IeaY/WtuapsrqRFxuQL9QFWN0FsRIdLmjTq+00+B/XnnKRKFBuWfjhHLF/uu8f+E 45 | t6Lf23Kb8yD6ZR7dihMZAGHnYQ/hlhM= 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | MIIDFDCCAfygAwIBAgIVAN3vv+b7KN5Se9m1RZsCllp/B/hdMA0GCSqGSIb3DQEB 56 | CwUAMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwHhcNMTUxMjExMDIyMDE0WhcNMzUx 57 | MjExMDIyMDE0WjAVMRMwEQYDVQQDDAppZHB0ZXN0YmVkMIIBIjANBgkqhkiG9w0B 58 | AQEFAAOCAQ8AMIIBCgKCAQEAh91caeY0Q85uhaUyqFwP2bMjwMFxMzRlAoqBHd7g 59 | u6eo4duaeLz1BaoR2XTBpNNvFR5oHH+TkKahVDGeH5+kcnIpxI8JPdsZml1srvf2 60 | Z6dzJsulJZUdpqnngycTkGtZgEoC1vmYVky2BSAIIifmdh6s0epbHnMGLsHzMKfJ 61 | Cb/Q6dYzRWTCPtzE2VMuQqqWgeyMr7u14x/Vqr9RPEFsgY8GIu5jzB6AyUIwrLg+ 62 | MNkv6aIdcHwxYTGL7ijfy6rSWrgBflQoYRYNEnseK0ZHgJahz4ovCag6wZAoPpBs 63 | uYlY7lEr89Ucb6NHx3uqGMsXlDFdE4QwfDLLhCYHPvJ0uwIDAQABo1swWTAdBgNV 64 | HQ4EFgQUAkOgED3iYdmvQEOMm6u/JmD/UTQwOAYDVR0RBDEwL4IKaWRwdGVzdGJl 65 | ZIYhaHR0cHM6Ly9pZHB0ZXN0YmVkL2lkcC9zaGliYm9sZXRoMA0GCSqGSIb3DQEB 66 | CwUAA4IBAQBIdd4YWlnvJjql8+zKKgmWgIY7U8DA8e6QcbAf8f8cdE33RSnjI63X 67 | sv/y9GfmbAVAD6RIAXPFFeRYJ08GOxGI9axfNaKdlsklJ9bk4ducHqgCSWYVer3s 68 | RQBjxyOfSTvk9YCJvdJVQRJLcCvxwKakFCsOSnV3t9OvN86Ak+fKPVB5j2fM/0fZ 69 | Kqjn3iqgdNPTLXPsuJLJO5lITRiBa4onmVelAiCstI9PQiaEck+oAHnMTnC9JE/B 70 | DHv3e4rwq3LznlqPw0GSd7xqNTdMDwNOWjkuOr3sGpWS8ms/ZHHXV1Vd22uPe70i 71 | s00xrv14zLifcc8oj5DYzOhYRifRXgHX 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | MIIDEzCCAfugAwIBAgIUG6Nn1rlERS1vsi88tcdzSYX0oqAwDQYJKoZIhvcNAQEL 82 | BQAwFTETMBEGA1UEAwwKaWRwdGVzdGJlZDAeFw0xNTEyMTEwMjIwMTRaFw0zNTEy 83 | MTEwMjIwMTRaMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwggEiMA0GCSqGSIb3DQEB 84 | AQUAA4IBDwAwggEKAoIBAQCBXv0o3fmT8iluyLjJ4lBAVCW+ZRVyEXPYQuRi7vfD 85 | cO4a6d1kxiJLsaK0W88VNxjFQRr8PgDkWr28vwoH1rgk4pLsszLD48DBzD942peJ 86 | l/S6FnsIJjmaHcBh4pbNhU4yowu63iKkvttrcZAEbpEro6Z8CziWEx8sywoaYEQG 87 | ifPkr9ORV6Cn3txq+9gMBePG41GrtZrUGIu+xrndL0Shh4Pq0eq/9MAsVlIIXEa8 88 | 9WfH8J2kFcTOfoWtIc70b7TLZQsx4YnNcnrGLSUEcstFyPLX+Xtv5SNZF89OOIxX 89 | VNjNvgE5DbJb9hMM4UAFqI+1bo9QqtxwThjc/sOvIxzNAgMBAAGjWzBZMB0GA1Ud 90 | DgQWBBStTyogRPuAVG6q7yPyav1uvE+7pTA4BgNVHREEMTAvggppZHB0ZXN0YmVk 91 | hiFodHRwczovL2lkcHRlc3RiZWQvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQEL 92 | BQADggEBAFMfoOv+oISGjvamq7+Y4G7ep5vxlAPeK3RATYPYvAmyH946qZXh98ni 93 | QXyuqZW5P5eEt86toY45IwDU5r09SKwHughEe99iiEkxh0mb2qo84qX9/qcg+kyN 94 | jeLd/OSyolpUCEFNwOFcog7pj7Eer+6AHbwTn1Mjb5TBsKwtDMJsaxPvdj0u7M5r 95 | xL/wHkFhn1rCo2QiojzjSlV3yLTh49iTyhE3cG+RxaNKDCxhp0jSSLX1BW/ZoPA8 96 | +PMJEA+Q0QbyRD8aJOHN5O8jGxCa/ZzcOnYVL6AsEXoDiY3vAUYh1FUonOWw0m9H 97 | p+tGUbGS2l873J5PrsbpeKEVR/IIoKo= 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 113 | 114 | urn:mace:shibboleth:1.0:nameIdentifier 115 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | example.org 129 | 130 | 131 | 132 | 133 | 134 | 135 | MIIDEzCCAfugAwIBAgIUS9SuTXwsFVVG+LjOEAbLqqT/el0wDQYJKoZIhvcNAQEL 136 | BQAwFTETMBEGA1UEAwwKaWRwdGVzdGJlZDAeFw0xNTEyMTEwMjIwMjZaFw0zNTEy 137 | MTEwMjIwMjZaMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwggEiMA0GCSqGSIb3DQEB 138 | AQUAA4IBDwAwggEKAoIBAQCMAoDHx8xCIfv/6QKqt9mcHYmEJ8y2dKprUbpdcOjH 139 | YvNPIl/lHPsUyrb+Nc+q2CDeiWjVk1mWYq0UpIwpBMuw1H6+oOqr4VQRi65pin0M 140 | SfE0MWIaFo5FPvpvoptkHD4gvREbm4swyXGMczcMRfqgalFXhUD2wz8W3XAM5Cq2 141 | 03XeJbj6TwjvKatG5XPdeUe2FBGuOO2q54L1hcIGnLMCQrg7D31lR13PJbjnJ0No 142 | 5C3k8TPuny6vJsBC03GNLNKfmrKVTdzr3VKp1uay1G3DL9314fgmbl8HA5iRQmy+ 143 | XInUU6/8NXZSF59p3ITAOvZQeZsbJjg5gGDip5OZo9YlAgMBAAGjWzBZMB0GA1Ud 144 | DgQWBBRPlM4VkKZ0U4ec9GrIhFQl0hNbLDA4BgNVHREEMTAvggppZHB0ZXN0YmVk 145 | hiFodHRwczovL2lkcHRlc3RiZWQvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQEL 146 | BQADggEBAIZ0a1ov3my3ljJG588I/PHx+TxAWONWmpKbO9c/qI3Drxk4oRIffiac 147 | ANxdvtabgIzrlk5gMMisD7oyqHJiWgKv5Bgctd8w3IS3lLl7wHX65mTKQRXniG98 148 | NIjkvfrhe2eeJxecOqnDI8GOhIGCIqZUn8ShdM/yHjhQ2Mh0Hj3U0LlKvnmfGSQl 149 | j0viGwbFCaNaIP3zc5UmCrdE5h8sWL3Fu7ILKM9RyFa2ILHrJScV9t623IcHffHP 150 | IeaY/WtuapsrqRFxuQL9QFWN0FsRIdLmjTq+00+B/XnnKRKFBuWfjhHLF/uu8f+E 151 | t6Lf23Kb8yD6ZR7dihMZAGHnYQ/hlhM= 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | MIIDFDCCAfygAwIBAgIVAN3vv+b7KN5Se9m1RZsCllp/B/hdMA0GCSqGSIb3DQEB 162 | CwUAMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwHhcNMTUxMjExMDIyMDE0WhcNMzUx 163 | MjExMDIyMDE0WjAVMRMwEQYDVQQDDAppZHB0ZXN0YmVkMIIBIjANBgkqhkiG9w0B 164 | AQEFAAOCAQ8AMIIBCgKCAQEAh91caeY0Q85uhaUyqFwP2bMjwMFxMzRlAoqBHd7g 165 | u6eo4duaeLz1BaoR2XTBpNNvFR5oHH+TkKahVDGeH5+kcnIpxI8JPdsZml1srvf2 166 | Z6dzJsulJZUdpqnngycTkGtZgEoC1vmYVky2BSAIIifmdh6s0epbHnMGLsHzMKfJ 167 | Cb/Q6dYzRWTCPtzE2VMuQqqWgeyMr7u14x/Vqr9RPEFsgY8GIu5jzB6AyUIwrLg+ 168 | MNkv6aIdcHwxYTGL7ijfy6rSWrgBflQoYRYNEnseK0ZHgJahz4ovCag6wZAoPpBs 169 | uYlY7lEr89Ucb6NHx3uqGMsXlDFdE4QwfDLLhCYHPvJ0uwIDAQABo1swWTAdBgNV 170 | HQ4EFgQUAkOgED3iYdmvQEOMm6u/JmD/UTQwOAYDVR0RBDEwL4IKaWRwdGVzdGJl 171 | ZIYhaHR0cHM6Ly9pZHB0ZXN0YmVkL2lkcC9zaGliYm9sZXRoMA0GCSqGSIb3DQEB 172 | CwUAA4IBAQBIdd4YWlnvJjql8+zKKgmWgIY7U8DA8e6QcbAf8f8cdE33RSnjI63X 173 | sv/y9GfmbAVAD6RIAXPFFeRYJ08GOxGI9axfNaKdlsklJ9bk4ducHqgCSWYVer3s 174 | RQBjxyOfSTvk9YCJvdJVQRJLcCvxwKakFCsOSnV3t9OvN86Ak+fKPVB5j2fM/0fZ 175 | Kqjn3iqgdNPTLXPsuJLJO5lITRiBa4onmVelAiCstI9PQiaEck+oAHnMTnC9JE/B 176 | DHv3e4rwq3LznlqPw0GSd7xqNTdMDwNOWjkuOr3sGpWS8ms/ZHHXV1Vd22uPe70i 177 | s00xrv14zLifcc8oj5DYzOhYRifRXgHX 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | MIIDEzCCAfugAwIBAgIUG6Nn1rlERS1vsi88tcdzSYX0oqAwDQYJKoZIhvcNAQEL 188 | BQAwFTETMBEGA1UEAwwKaWRwdGVzdGJlZDAeFw0xNTEyMTEwMjIwMTRaFw0zNTEy 189 | MTEwMjIwMTRaMBUxEzARBgNVBAMMCmlkcHRlc3RiZWQwggEiMA0GCSqGSIb3DQEB 190 | AQUAA4IBDwAwggEKAoIBAQCBXv0o3fmT8iluyLjJ4lBAVCW+ZRVyEXPYQuRi7vfD 191 | cO4a6d1kxiJLsaK0W88VNxjFQRr8PgDkWr28vwoH1rgk4pLsszLD48DBzD942peJ 192 | l/S6FnsIJjmaHcBh4pbNhU4yowu63iKkvttrcZAEbpEro6Z8CziWEx8sywoaYEQG 193 | ifPkr9ORV6Cn3txq+9gMBePG41GrtZrUGIu+xrndL0Shh4Pq0eq/9MAsVlIIXEa8 194 | 9WfH8J2kFcTOfoWtIc70b7TLZQsx4YnNcnrGLSUEcstFyPLX+Xtv5SNZF89OOIxX 195 | VNjNvgE5DbJb9hMM4UAFqI+1bo9QqtxwThjc/sOvIxzNAgMBAAGjWzBZMB0GA1Ud 196 | DgQWBBStTyogRPuAVG6q7yPyav1uvE+7pTA4BgNVHREEMTAvggppZHB0ZXN0YmVk 197 | hiFodHRwczovL2lkcHRlc3RiZWQvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQEL 198 | BQADggEBAFMfoOv+oISGjvamq7+Y4G7ep5vxlAPeK3RATYPYvAmyH946qZXh98ni 199 | QXyuqZW5P5eEt86toY45IwDU5r09SKwHughEe99iiEkxh0mb2qo84qX9/qcg+kyN 200 | jeLd/OSyolpUCEFNwOFcog7pj7Eer+6AHbwTn1Mjb5TBsKwtDMJsaxPvdj0u7M5r 201 | xL/wHkFhn1rCo2QiojzjSlV3yLTh49iTyhE3cG+RxaNKDCxhp0jSSLX1BW/ZoPA8 202 | +PMJEA+Q0QbyRD8aJOHN5O8jGxCa/ZzcOnYVL6AsEXoDiY3vAUYh1FUonOWw0m9H 203 | p+tGUbGS2l873J5PrsbpeKEVR/IIoKo= 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /src/main/java/net/unicon/idp/externalauth/ShibcasAuthServlet.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.externalauth; 2 | 3 | import net.shibboleth.idp.authn.AuthnEventIds; 4 | import net.shibboleth.idp.authn.ExternalAuthentication; 5 | import net.shibboleth.idp.authn.ExternalAuthenticationException; 6 | import net.unicon.idp.authn.provider.extra.EntityIdParameterBuilder; 7 | import net.unicon.idp.authn.provider.extra.IParameterBuilder; 8 | import org.apache.commons.lang.StringUtils; 9 | import org.jasig.cas.client.util.CommonUtils; 10 | import org.jasig.cas.client.validation.AbstractCasProtocolUrlBasedTicketValidator; 11 | import org.jasig.cas.client.validation.Assertion; 12 | import org.jasig.cas.client.validation.Cas10TicketValidator; 13 | import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; 14 | import org.jasig.cas.client.validation.Cas30ServiceTicketValidator; 15 | import org.jasig.cas.client.validation.TicketValidationException; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.context.ApplicationContext; 19 | import org.springframework.context.ApplicationContextAware; 20 | import org.springframework.context.EnvironmentAware; 21 | import org.springframework.core.env.Environment; 22 | import org.springframework.web.context.WebApplicationContext; 23 | 24 | import javax.servlet.RequestDispatcher; 25 | import javax.servlet.ServletConfig; 26 | import javax.servlet.ServletException; 27 | import javax.servlet.annotation.WebServlet; 28 | import javax.servlet.http.HttpServlet; 29 | import javax.servlet.http.HttpServletRequest; 30 | import javax.servlet.http.HttpServletResponse; 31 | import java.io.IOException; 32 | import java.util.HashSet; 33 | import java.util.Set; 34 | 35 | 36 | /** 37 | * A Servlet that validates the CAS ticket and then pushes the authenticated principal name into the correct location before 38 | * handing back control to Shib 39 | * 40 | * @author chasegawa@unicon.net 41 | * @author jgasper@unicon.net 42 | * @author aremmes (GitHub) 43 | */ 44 | @WebServlet(name = "ShibcasAuthServlet", urlPatterns = {"/Authn/External/*"}) 45 | public class ShibcasAuthServlet extends HttpServlet { 46 | private final Logger logger = LoggerFactory.getLogger(ShibcasAuthServlet.class); 47 | private static final long serialVersionUID = 1L; 48 | private static final String artifactParameterName = "ticket"; 49 | private static final String serviceParameterName = "service"; 50 | 51 | private String casLoginUrl; 52 | private String serverName; 53 | private String casServerPrefix; 54 | private String ticketValidatorName; 55 | private String entityIdLocation; 56 | 57 | private AbstractCasProtocolUrlBasedTicketValidator ticketValidator; 58 | 59 | private final Set translators = new HashSet(); 60 | private final Set parameterBuilders = new HashSet(); 61 | 62 | @Override 63 | protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 64 | // TODO: We have the opportunity to give back more to Shib than just the PRINCIPAL_NAME_KEY. Identify additional information 65 | try { 66 | final String ticket = CommonUtils.safeGetParameter(request, artifactParameterName); 67 | final String gatewayAttempted = CommonUtils.safeGetParameter(request, "gatewayAttempted"); 68 | final String authenticationKey = ExternalAuthentication.startExternalAuthentication(request); 69 | final boolean force = Boolean.parseBoolean(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM).toString()); 70 | final boolean passive = Boolean.parseBoolean(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM).toString()); 71 | 72 | if ((ticket == null || ticket.isEmpty()) && (gatewayAttempted == null || gatewayAttempted.isEmpty())) { 73 | logger.debug("ticket and gatewayAttempted are not set; initiating CAS login redirect"); 74 | startLoginRequest(request, response, force, passive, authenticationKey); 75 | return; 76 | } 77 | 78 | if (ticket == null || ticket.isEmpty()) { 79 | logger.debug("Gateway/Passive returned no ticket, returning NoPassive."); 80 | request.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, AuthnEventIds.NO_PASSIVE); 81 | ExternalAuthentication.finishExternalAuthentication(authenticationKey, request, response); 82 | return; 83 | } 84 | 85 | validatevalidateCasTicket(request, response, ticket, authenticationKey, force); 86 | 87 | } catch (final ExternalAuthenticationException e) { 88 | logger.warn("Error processing ShibCas authentication request", e); 89 | loadErrorPage(request, response); 90 | 91 | } catch (final Exception e) { 92 | logger.error("Something unexpected happened", e); 93 | request.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, AuthnEventIds.AUTHN_EXCEPTION); 94 | } 95 | } 96 | 97 | private void validatevalidateCasTicket(final HttpServletRequest request, final HttpServletResponse response, final String ticket, 98 | final String authenticationKey, final boolean force) throws ExternalAuthenticationException, IOException { 99 | try { 100 | ticketValidator.setRenew(force); 101 | final String serviceUrl = constructServiceUrl(request, response, true); 102 | logger.debug("validating ticket: {} with service url: {}", ticket, serviceUrl); 103 | final Assertion assertion = ticketValidator.validate(ticket, serviceUrl); 104 | if (assertion == null) { 105 | throw new TicketValidationException("Validation failed. Assertion could not be retrieved for ticket " + ticket); 106 | } 107 | for (final CasToShibTranslator casToShibTranslator : translators) { 108 | casToShibTranslator.doTranslation(request, response, assertion, authenticationKey); 109 | } 110 | } catch (final Exception e) { 111 | logger.error("Ticket validation failed, returning InvalidTicket", e); 112 | request.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, "InvalidTicket"); 113 | } 114 | ExternalAuthentication.finishExternalAuthentication(authenticationKey, request, response); 115 | } 116 | 117 | protected void startLoginRequest(final HttpServletRequest request, final HttpServletResponse response, 118 | final Boolean force, final Boolean passive, String authenticationKey) { 119 | // CAS Protocol - http://www.jasig.org/cas/protocol indicates not setting gateway if renew has been set. 120 | // we will set both and let CAS sort it out, but log a warning 121 | if (Boolean.TRUE.equals(passive) && Boolean.TRUE.equals(force)) { 122 | logger.warn("Both FORCE AUTHN and PASSIVE AUTHN were set to true, please verify that the requesting system has been properly configured."); 123 | } 124 | 125 | try { 126 | String serviceUrl = constructServiceUrl(request, response); 127 | if (passive) { 128 | serviceUrl += "&gatewayAttempted=true"; 129 | } 130 | 131 | final String loginUrl = constructRedirectUrl(serviceUrl, force, passive) + getAdditionalParameters(request, authenticationKey); 132 | logger.debug("loginUrl: {}", loginUrl); 133 | response.sendRedirect(loginUrl); 134 | } catch (final IOException e) { 135 | logger.error("Unable to redirect to CAS from ShibCas", e); 136 | } 137 | } 138 | 139 | /** 140 | * Uses the CAS CommonUtils to build the CAS Redirect URL. 141 | */ 142 | private String constructRedirectUrl(final String serviceUrl, final boolean renew, final boolean gateway) { 143 | return CommonUtils.constructRedirectUrl(casLoginUrl, "service", serviceUrl, renew, gateway, null); 144 | } 145 | 146 | /** 147 | * Build addition querystring parameters 148 | * 149 | * @param request The original servlet request 150 | * @return an ampersand delimited list of querystring parameters 151 | */ 152 | private String getAdditionalParameters(final HttpServletRequest request, final String authenticationKey) { 153 | final StringBuilder builder = new StringBuilder(); 154 | for (final IParameterBuilder paramBuilder : parameterBuilders) { 155 | builder.append(paramBuilder.getParameterString(request, authenticationKey)); 156 | } 157 | return builder.toString(); 158 | } 159 | 160 | @Override 161 | public void init(final ServletConfig config) throws ServletException { 162 | super.init(config); 163 | 164 | final ApplicationContext ac = (ApplicationContext) config.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 165 | parseProperties(ac.getEnvironment()); 166 | 167 | switch (ticketValidatorName) { 168 | case "cas10": 169 | ticketValidator = new Cas10TicketValidator(casServerPrefix); 170 | break; 171 | case "cas30": 172 | ticketValidator = new Cas30ServiceTicketValidator(casServerPrefix); 173 | break; 174 | case "cas20": 175 | ticketValidator = new Cas20ServiceTicketValidator(casServerPrefix); 176 | } 177 | 178 | if (ticketValidator == null) { 179 | throw new ServletException("Initialization failed. Invalid shibcas.ticketValidatorName property: '" 180 | + ticketValidatorName + "'"); 181 | } 182 | 183 | if ("append".equalsIgnoreCase(entityIdLocation)) { 184 | parameterBuilders.add(new EntityIdParameterBuilder()); 185 | } 186 | 187 | buildTranslators(ac.getEnvironment()); 188 | buildParameterBuilders(ac); 189 | } 190 | 191 | /** 192 | * Check the idp's idp.properties file for the configuration 193 | * 194 | * @param environment a Spring Application Context's Environment object (tied to the IdP's root context) 195 | */ 196 | private void parseProperties(final Environment environment) { 197 | logger.debug("reading properties from the idp.properties file"); 198 | casServerPrefix = environment.getRequiredProperty("shibcas.casServerUrlPrefix"); 199 | logger.debug("shibcas.casServerUrlPrefix: {}", casServerPrefix); 200 | 201 | casLoginUrl = environment.getRequiredProperty("shibcas.casServerLoginUrl"); 202 | logger.debug("shibcas.casServerLoginUrl: {}", casLoginUrl); 203 | 204 | serverName = environment.getRequiredProperty("shibcas.serverName"); 205 | logger.debug("shibcas.serverName: {}", serverName); 206 | 207 | ticketValidatorName = environment.getProperty("shibcas.ticketValidatorName", "cas30"); 208 | logger.debug("shibcas.ticketValidatorName: {}", ticketValidatorName); 209 | 210 | entityIdLocation = environment.getProperty("shibcas.entityIdLocation", "append"); 211 | logger.debug("shibcas.entityIdLocation: {}", entityIdLocation); 212 | } 213 | 214 | private void buildParameterBuilders(final ApplicationContext applicationContext) { 215 | final Environment environment = applicationContext.getEnvironment(); 216 | final String builders = StringUtils.defaultString(environment.getProperty("shibcas.parameterBuilders", "")); 217 | for (final String parameterBuilder : StringUtils.split(builders, ";")) { 218 | try { 219 | logger.debug("Loading parameter builder class {}", parameterBuilder); 220 | final Class clazz = Class.forName(parameterBuilder); 221 | final IParameterBuilder builder = IParameterBuilder.class.cast(clazz.newInstance()); 222 | if (builder instanceof ApplicationContextAware) { 223 | ((ApplicationContextAware) builder).setApplicationContext(applicationContext); 224 | } 225 | this.parameterBuilders.add(builder); 226 | logger.debug("Added parameter builder {}", parameterBuilder); 227 | } catch (final Throwable e) { 228 | logger.error("Error building parameter builder with name: " + parameterBuilder, e); 229 | } 230 | } 231 | } 232 | 233 | /** 234 | * Attempt to build the set of translators from the fully qualified class names set in the properties. If nothing has been set 235 | * then default to the AuthenticatedNameTranslator only. 236 | */ 237 | private void buildTranslators(final Environment environment) { 238 | translators.add(new AuthenticatedNameTranslator()); 239 | 240 | final String casToShibTranslators = StringUtils.defaultString(environment.getProperty("shibcas.casToShibTranslators", "")); 241 | for (final String classname : StringUtils.split(casToShibTranslators, ';')) { 242 | try { 243 | logger.debug("Loading translator class {}", classname); 244 | final Class c = Class.forName(classname); 245 | final CasToShibTranslator e = (CasToShibTranslator) c.newInstance(); 246 | if (e instanceof EnvironmentAware) { 247 | ((EnvironmentAware) e).setEnvironment(environment); 248 | } 249 | translators.add(e); 250 | logger.debug("Added translator class {}", classname); 251 | } catch (final Exception e) { 252 | logger.error("Error building cas to shib translator with name: " + classname, e); 253 | } 254 | } 255 | } 256 | 257 | /** 258 | * Use the CAS CommonUtils to build the CAS Service URL. 259 | */ 260 | protected String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) { 261 | String serviceUrl = CommonUtils.constructServiceUrl(request, response, null, serverName, 262 | serviceParameterName, artifactParameterName, true); 263 | 264 | if ("embed".equalsIgnoreCase(entityIdLocation)) { 265 | serviceUrl += (new EntityIdParameterBuilder().getParameterString(request, false)); 266 | } 267 | 268 | 269 | return serviceUrl; 270 | } 271 | 272 | /** 273 | * Like the above, but with a flag indicating whether we're validating a service ticket, 274 | * in which case we should not modify the service URL returned by CAS CommonUtils; this 275 | * avoids appending the entity ID twice when entityIdLocation=embed, since the ID is already 276 | * embedded in the string during validation. 277 | */ 278 | protected String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response, final boolean isValidatingTicket) { 279 | return isValidatingTicket 280 | ? CommonUtils.constructServiceUrl(request, response, null, serverName, serviceParameterName, artifactParameterName, true) 281 | : constructServiceUrl(request, response); 282 | } 283 | 284 | private void loadErrorPage(final HttpServletRequest request, final HttpServletResponse response) { 285 | final RequestDispatcher requestDispatcher = request.getRequestDispatcher("/no-conversation-state.jsp"); 286 | try { 287 | requestDispatcher.forward(request, response); 288 | } catch (final Exception e) { 289 | logger.error("Error rendering the empty conversation state (shib-cas-authn3) error view."); 290 | response.resetBuffer(); 291 | response.setStatus(404); 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/test/java/net/unicon/idp/externalauth/ShibcasAuthServletTest.java: -------------------------------------------------------------------------------- 1 | package net.unicon.idp.externalauth; 2 | 3 | import net.shibboleth.idp.authn.ExternalAuthentication; 4 | import net.shibboleth.idp.authn.ExternalAuthenticationException; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.jasig.cas.client.authentication.AttributePrincipal; 7 | import org.jasig.cas.client.validation.Assertion; 8 | import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; 9 | import org.jasig.cas.client.validation.Cas30ServiceTicketValidator; 10 | import org.jasig.cas.client.validation.TicketValidationException; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.BDDMockito; 14 | import org.mockito.Mockito; 15 | import org.powermock.api.mockito.PowerMockito; 16 | import org.powermock.api.support.membermodification.MemberModifier; 17 | import org.powermock.core.classloader.annotations.PrepareForTest; 18 | import org.powermock.modules.junit4.PowerMockRunner; 19 | import org.springframework.context.ApplicationContext; 20 | import org.springframework.core.env.Environment; 21 | import org.springframework.web.context.WebApplicationContext; 22 | 23 | import javax.servlet.ServletConfig; 24 | import javax.servlet.ServletContext; 25 | import javax.servlet.ServletException; 26 | import javax.servlet.http.HttpServletRequest; 27 | import javax.servlet.http.HttpServletResponse; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.mockito.Mockito.*; 31 | 32 | @RunWith(PowerMockRunner.class) 33 | @PrepareForTest({ExternalAuthentication.class, Cas20ServiceTicketValidator.class}) 34 | public class ShibcasAuthServletTest { 35 | private String CONVERSATION = "conversation=e1s1"; 36 | private String CONVERSATION_TICKET = "conversation=e1s1&ticket=ST-1234-123456789-a"; 37 | private String CONVERSATION_TICKET_GATEWAY_ATTEMPTED = "conversation=e1s1&ticket=ST-1234-123456789-a&gatewayAttempted=true"; 38 | private String CONVERSATION_TICKET_EMBEDDED_ENTITYID = "conversation=e1s1&ticket=ST-1234-123456789-a&entityId=http://test.edu/sp"; 39 | private String E1S1 = "E1S1"; 40 | private String JDOE = "jdoe"; 41 | private String TICKET = "ST-1234-123456789-a"; 42 | private String URL_WITH_CONVERSATION = "https://shibserver.example.edu/idp/Authn/ExtCas?conversation=e1s1"; 43 | private String URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED = "https://shibserver.example.edu/idp/Authn/ExtCas?conversation=e1s1&gatewayAttempted=true"; 44 | private String URL_WITH_CONVERSATION_EMBEDDED_ENTITYID = "https://shibserver.example.edu/idp/Authn/ExtCas?conversation=e1s1&entityId=http%3A%2F%2Ftest.edu%2Fsp"; 45 | 46 | @Test 47 | public void testDoGetStandard() throws Exception { 48 | //Mock some objects. 49 | final HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET, TICKET, null); 50 | final HttpServletResponse response = createMockHttpServletResponse(); 51 | final Assertion assertion = createMockAssertion(); 52 | 53 | final Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class); 54 | PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION)).thenReturn(assertion); 55 | 56 | PowerMockito.mockStatic(ExternalAuthentication.class); 57 | BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1); 58 | 59 | //Prep our object 60 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 61 | 62 | //Override the internal Cas20TicketValidator because we don't want it to call a real server 63 | MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator); 64 | 65 | //Standard request/response 66 | BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false"); 67 | BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("false"); 68 | shibcasAuthServlet.doGet(request, response); 69 | 70 | //Verify 71 | verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE); 72 | } 73 | 74 | 75 | @Test 76 | public void testDoGetBadTicket() throws Exception { 77 | //Mock some objects. 78 | final HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET, TICKET, "false"); 79 | final HttpServletResponse response = createMockHttpServletResponse(); 80 | final Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class); 81 | PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION)).thenThrow(new TicketValidationException("Invalid Ticket")); 82 | 83 | PowerMockito.mockStatic(ExternalAuthentication.class); 84 | BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willThrow(new ExternalAuthenticationException()); 85 | 86 | //Prep our object 87 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 88 | 89 | //Override the internal Cas20TicketValidator because we don't want it to call a real server 90 | MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator); 91 | 92 | //Standard request/response - bad ticket 93 | BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false"); 94 | BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("false"); 95 | shibcasAuthServlet.doGet(request, response); 96 | 97 | //Verify 98 | verify(request).getRequestDispatcher("/no-conversation-state.jsp"); 99 | verify(response).setStatus(404); 100 | } 101 | 102 | 103 | @Test 104 | public void testDoGetPassiveAuthenticated() throws Exception { 105 | //Mock some objects. 106 | final HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET + "&gatewayAttempted=true", TICKET, "true"); 107 | final HttpServletResponse response = createMockHttpServletResponse(); 108 | final Assertion assertion = createMockAssertion(); 109 | 110 | final Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class); 111 | PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED)).thenReturn(assertion); 112 | 113 | PowerMockito.mockStatic(ExternalAuthentication.class); 114 | BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1); 115 | 116 | //Prep our object 117 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 118 | 119 | //Override the internal Cas20TicketValidator because we don't want it to call a real server 120 | MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator); 121 | 122 | //Passive request/response with authenticated user 123 | BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false"); 124 | BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("true"); 125 | shibcasAuthServlet.doGet(request, response); 126 | 127 | //Verify 128 | verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE); 129 | } 130 | 131 | @Test 132 | public void testDoGetPassiveNotAuthenticated() throws Exception { 133 | //Mock some objects. 134 | final HttpServletRequest request = createDoGetHttpServletRequest("conversation=e1s1&gatewayAttempted=true", null, "true"); 135 | final HttpServletResponse response = createMockHttpServletResponse(); 136 | 137 | final Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class); 138 | 139 | PowerMockito.mockStatic(ExternalAuthentication.class); 140 | BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1); 141 | 142 | //Prep our object 143 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 144 | 145 | //Override the internal Cas20TicketValidator because we don't want it to call a real server 146 | MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator); 147 | 148 | //Passive request/response with no user 149 | BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false"); 150 | BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("true"); 151 | shibcasAuthServlet.doGet(request, response); 152 | 153 | //Verify 154 | verify(request, never()).setAttribute(eq(ExternalAuthentication.PRINCIPAL_NAME_KEY), any()); 155 | verify(request).setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, "NoPassive"); 156 | verify(ticketValidator, never()).validate(anyString(), anyString()); 157 | } 158 | 159 | @Test 160 | public void testDoGetForced() throws Exception { 161 | //Mock some objects. 162 | final HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET, TICKET, null); 163 | final HttpServletResponse response = createMockHttpServletResponse(); 164 | final Assertion assertion = createMockAssertion(); 165 | 166 | final Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class); 167 | PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION)).thenReturn(assertion); 168 | 169 | PowerMockito.mockStatic(ExternalAuthentication.class); 170 | BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1); 171 | 172 | //Prep our object 173 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 174 | 175 | //Override the internal Cas20TicketValidator because we don't want it to call a real server 176 | MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator); 177 | 178 | //Forced request/response 179 | BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("true"); 180 | BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("false"); 181 | shibcasAuthServlet.doGet(request, response); 182 | 183 | //Verify 184 | verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE); 185 | } 186 | 187 | @Test 188 | public void testDoGetPassiveAndForced() throws Exception { 189 | //Mock some objects. 190 | final HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET_GATEWAY_ATTEMPTED, TICKET, "true"); 191 | final HttpServletResponse response = createMockHttpServletResponse(); 192 | final Assertion assertion = createMockAssertion(); 193 | 194 | final Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas30ServiceTicketValidator.class); 195 | PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED)).thenReturn(assertion); 196 | 197 | PowerMockito.mockStatic(ExternalAuthentication.class); 198 | BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1); 199 | 200 | //Prep our object 201 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 202 | 203 | //Override the internal Cas30TicketValidator because we don't want it to call a real server 204 | MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator); 205 | 206 | //Passive and forced request/response 207 | BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("true"); 208 | BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("true"); 209 | shibcasAuthServlet.doGet(request, response); 210 | 211 | //Verify 212 | verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE); 213 | } 214 | 215 | 216 | @Test 217 | public void testConstructServiceUrlAppend() throws Exception { 218 | //Mock some objects. 219 | final HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET, TICKET, "false"); 220 | final HttpServletResponse response = createMockHttpServletResponse(); 221 | 222 | //Prep our object 223 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 224 | 225 | final String result = shibcasAuthServlet.constructServiceUrl(request, response); 226 | 227 | assertEquals("https://shibserver.example.edu/idp/Authn/ExtCas?conversation=e1s1", result); 228 | } 229 | 230 | @Test 231 | public void testConstructServiceUrlEmbed() throws Exception { 232 | //Mock some objects. 233 | final HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET + "&entityId=http://test.edu/sp", TICKET, "false"); 234 | final HttpServletResponse response = createMockHttpServletResponse(); 235 | 236 | //Prep our object 237 | final ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet(); 238 | shibcasAuthServlet.init(createMockServletConfig("embed")); 239 | 240 | final String result = shibcasAuthServlet.constructServiceUrl(request, response, true); 241 | 242 | assertEquals("https://shibserver.example.edu/idp/Authn/ExtCas?conversation=e1s1&entityId=http%3A%2F%2Ftest.edu%2Fsp", result); 243 | } 244 | 245 | 246 | @Test 247 | public void testStartLoginRequestStandard() throws Exception { 248 | final HttpServletRequest request = createMockHttpServletRequest(); 249 | BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION); 250 | 251 | final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 252 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION); 253 | 254 | final ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet(); 255 | shibcasAuthServlet.init(createMockServletConfig()); 256 | 257 | shibcasAuthServlet.startLoginRequest(request, response, false, false, ""); 258 | verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1&entityId=http%3A%2F%2Ftest.edu%2Fsp"); 259 | } 260 | 261 | @Test 262 | public void testStartLoginRequestEmbeddedEntityId() throws Exception { 263 | final HttpServletRequest request = createMockHttpServletRequest(); 264 | BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION); 265 | 266 | final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 267 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION); 268 | 269 | final ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet(); 270 | shibcasAuthServlet.init(createMockServletConfig("embed")); 271 | 272 | shibcasAuthServlet.startLoginRequest(request, response, false, false, ""); 273 | verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1%26entityId%3Dhttp%3A%2F%2Ftest.edu%2Fsp"); 274 | } 275 | 276 | @Test 277 | public void testStartLoginRequestAppendedEntityId() throws Exception { 278 | final HttpServletRequest request = createMockHttpServletRequest(); 279 | BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION); 280 | 281 | final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 282 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION); 283 | 284 | final ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet(); 285 | shibcasAuthServlet.init(createMockServletConfig("append")); 286 | 287 | shibcasAuthServlet.startLoginRequest(request, response, false, false, ""); 288 | verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1&entityId=http%3A%2F%2Ftest.edu%2Fsp"); 289 | } 290 | 291 | @Test 292 | public void testStartLoginRequestPassive() throws Exception { 293 | final HttpServletRequest request = createMockHttpServletRequest(); 294 | BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION); 295 | 296 | final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 297 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION); 298 | 299 | final ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet(); 300 | shibcasAuthServlet.init(createMockServletConfig()); 301 | 302 | //Passive 303 | shibcasAuthServlet.startLoginRequest(request, response, false, true, ""); 304 | verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1%26gatewayAttempted%3Dtrue&gateway=true&entityId=http%3A%2F%2Ftest.edu%2Fsp"); 305 | } 306 | 307 | @Test 308 | public void testStartLoginRequestForced() throws Exception { 309 | final HttpServletRequest request = createMockHttpServletRequest(); 310 | BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION); 311 | 312 | final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 313 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION); 314 | 315 | final ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet(); 316 | shibcasAuthServlet.init(createMockServletConfig()); 317 | 318 | //Forced 319 | shibcasAuthServlet.startLoginRequest(request, response, true, false, ""); 320 | verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1&renew=true&entityId=http%3A%2F%2Ftest.edu%2Fsp"); 321 | } 322 | 323 | @Test 324 | public void testStartLoginRequestPassiveAndForced() throws Exception { 325 | final HttpServletRequest request = createMockHttpServletRequest(); 326 | BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION); 327 | 328 | final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 329 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION); 330 | 331 | final ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet(); 332 | shibcasAuthServlet.init(createMockServletConfig()); 333 | 334 | //Passive and Forced 335 | shibcasAuthServlet.startLoginRequest(request, response, true, true, ""); 336 | verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1%26gatewayAttempted%3Dtrue&renew=true&gateway=true&entityId=http%3A%2F%2Ftest.edu%2Fsp"); 337 | } 338 | 339 | private HttpServletRequest createDoGetHttpServletRequest(final String queryString, final String ticket, final String gatewayAttempted) { 340 | final HttpServletRequest request = createMockHttpServletRequest(); 341 | 342 | BDDMockito.given(request.getQueryString()).willReturn(queryString); 343 | BDDMockito.given(request.getParameter("ticket")).willReturn(ticket); 344 | BDDMockito.given(request.getParameter("gatewayAttempted")).willReturn(gatewayAttempted); 345 | 346 | return request; 347 | } 348 | 349 | private Assertion createMockAssertion() { 350 | final Assertion assertion = Mockito.mock(Assertion.class); 351 | final AttributePrincipal attributePrincipal = Mockito.mock(AttributePrincipal.class); 352 | 353 | BDDMockito.given(attributePrincipal.getName()).willReturn(JDOE); 354 | BDDMockito.given(assertion.getPrincipal()).willReturn(attributePrincipal); 355 | 356 | return assertion; 357 | } 358 | 359 | 360 | private ServletConfig createMockServletConfig() { 361 | return createMockServletConfig(""); 362 | } 363 | 364 | private ServletConfig createMockServletConfig(final String entityIdLocation) { 365 | final ServletConfig config = Mockito.mock(ServletConfig.class); 366 | final ServletContext servletContext = Mockito.mock(ServletContext.class); 367 | final ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); 368 | final Environment environment = Mockito.mock(Environment.class); 369 | 370 | BDDMockito.given(config.getServletContext()).willReturn(servletContext); 371 | BDDMockito.given(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)).willReturn(applicationContext); 372 | BDDMockito.given(applicationContext.getEnvironment()).willReturn(environment); 373 | BDDMockito.given(environment.getRequiredProperty("shibcas.casServerUrlPrefix")).willReturn("https://cassserver.example.edu/cas"); 374 | BDDMockito.given(environment.getRequiredProperty("shibcas.casServerLoginUrl")).willReturn("https://cassserver.example.edu/cas/login"); 375 | BDDMockito.given(environment.getRequiredProperty("shibcas.serverName")).willReturn("https://shibserver.example.edu"); 376 | BDDMockito.given(environment.getRequiredProperty("shibcas.casToShibTranslators")).willReturn(null); 377 | BDDMockito.given(environment.getProperty("shibcas.ticketValidatorName", "cas30")).willReturn("cas30"); 378 | 379 | if (StringUtils.isNotEmpty(entityIdLocation)) { 380 | BDDMockito.given(environment.getProperty("shibcas.entityIdLocation", "append")).willReturn(entityIdLocation); 381 | } else { 382 | BDDMockito.given(environment.getProperty("shibcas.entityIdLocation", "append")).willReturn("append"); 383 | } 384 | 385 | return config; 386 | } 387 | 388 | private HttpServletRequest createMockHttpServletRequest() { 389 | try { 390 | final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 391 | 392 | BDDMockito.given(request.getScheme()).willReturn("http"); 393 | BDDMockito.given(request.getMethod()).willReturn("GET"); 394 | BDDMockito.given(request.isSecure()).willReturn(true); 395 | BDDMockito.given(request.getHeader("Host")).willReturn("shibserver.example.edu"); 396 | //BDDMockito.given(request.getHeader("X-Forwarded-Host")).willReturn(); 397 | BDDMockito.given(request.getServerPort()).willReturn(443); 398 | BDDMockito.given(request.getRequestURI()).willReturn("/idp/Authn/ExtCas"); 399 | BDDMockito.given(request.getRequestURL()).willReturn(new StringBuffer("/idp/Authn/ExtCas")); 400 | BDDMockito.given(request.getAttribute(ExternalAuthentication.RELYING_PARTY_PARAM)).willReturn("http://test.edu/sp"); 401 | 402 | return request; 403 | } catch (final Exception e) { 404 | throw new RuntimeException(e); 405 | } 406 | } 407 | 408 | private HttpServletResponse createMockHttpServletResponse() { 409 | final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 410 | 411 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION); 412 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED)).willReturn(URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED); 413 | BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION_EMBEDDED_ENTITYID)).willReturn(URL_WITH_CONVERSATION_EMBEDDED_ENTITYID); 414 | 415 | return response; 416 | } 417 | 418 | private ShibcasAuthServlet createShibcasAuthServlet() throws ServletException { 419 | final ShibcasAuthServlet shibcasAuthServlet; 420 | shibcasAuthServlet = new ShibcasAuthServlet(); 421 | shibcasAuthServlet.init(createMockServletConfig()); 422 | 423 | return shibcasAuthServlet; 424 | } 425 | 426 | } 427 | --------------------------------------------------------------------------------